summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDJ Mountney <dj@gitlab.com>2017-07-10 21:20:52 +0000
committerDJ Mountney <dj@gitlab.com>2017-07-10 21:20:52 +0000
commite643c0db35617d2b0ba607708322e827c15ce132 (patch)
tree2094291da16a3c4021176f7441b865970cfeccf8
parentba4250c119df661f32bc7e72b8001798464a9785 (diff)
parent26b6e299e1c97b956306572c5c9f6c02f428bfd7 (diff)
downloadgitlab-ce-e643c0db35617d2b0ba607708322e827c15ce132.tar.gz
Merge branch 'master' into 'catch-redis-address-error'
# Conflicts: # lib/gitlab/current_settings.rb
-rw-r--r--.eslintrc1
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml14
-rw-r--r--.rspec2
-rw-r--r--.rubocop.yml10
-rw-r--r--.rubocop_todo.yml10
-rw-r--r--CHANGELOG.md26
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock19
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/awards_handler.js144
-rw-r--r--app/assets/javascripts/behaviors/autosize.js23
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js94
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js11
-rw-r--r--app/assets/javascripts/behaviors/index.js2
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js5
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js3
-rw-r--r--app/assets/javascripts/build.js178
-rw-r--r--app/assets/javascripts/close_reopen_report_toggle.js97
-rw-r--r--app/assets/javascripts/comment_type_toggle.js5
-rw-r--r--app/assets/javascripts/diff.js12
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js4
-rw-r--r--app/assets/javascripts/dispatcher.js46
-rw-r--r--app/assets/javascripts/emoji/index.js99
-rw-r--r--app/assets/javascripts/emoji/support/index.js10
-rw-r--r--app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js (renamed from app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js)2
-rw-r--r--app/assets/javascripts/emoji/support/unicode_support_map.js (renamed from app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js)7
-rw-r--r--app/assets/javascripts/environments/components/environment.vue28
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue8
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js22
-rw-r--r--app/assets/javascripts/files_comment_button.js193
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js7
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js32
-rw-r--r--app/assets/javascripts/gl_form.js6
-rw-r--r--app/assets/javascripts/group_name.js23
-rw-r--r--app/assets/javascripts/helpers/issuables_helper.js27
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js26
-rw-r--r--app/assets/javascripts/issuable_form.js2
-rw-r--r--app/assets/javascripts/issue.js55
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue3
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue13
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js8
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js24
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js7
-rw-r--r--app/assets/javascripts/locale/eo/app.js1
-rw-r--r--app/assets/javascripts/main.js19
-rw-r--r--app/assets/javascripts/merge_request.js14
-rw-r--r--app/assets/javascripts/merge_request_tabs.js17
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring.vue157
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue297
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_deployment.vue136
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_flag.vue104
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_legends.vue144
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_row.vue41
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_state.vue112
-rw-r--r--app/assets/javascripts/monitoring/deployments.js211
-rw-r--r--app/assets/javascripts/monitoring/event_hub.js3
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js46
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js14
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js433
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js19
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js61
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js40
-rw-r--r--app/assets/javascripts/notes.js29
-rw-r--r--app/assets/javascripts/oauth_remember_me.js32
-rw-r--r--app/assets/javascripts/peek.js16
-rw-r--r--app/assets/javascripts/performance_bar.js62
-rw-r--r--app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js3
-rw-r--r--app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js71
-rw-r--r--app/assets/javascripts/project_new.js4
-rw-r--r--app/assets/javascripts/right_sidebar.js23
-rw-r--r--app/assets/javascripts/shortcuts.js2
-rw-r--r--app/assets/javascripts/sidebar_height_manager.js33
-rw-r--r--app/assets/javascripts/signin_tabs_memoizer.js82
-rw-r--r--app/assets/javascripts/single_file_diff.js149
-rw-r--r--app/assets/javascripts/smart_interval.js253
-rw-r--r--app/assets/javascripts/snippets_list.js18
-rw-r--r--app/assets/javascripts/star.js50
-rw-r--r--app/assets/javascripts/subscription.js74
-rw-r--r--app/assets/javascripts/subscription_select.js61
-rw-r--r--app/assets/javascripts/syntax_highlight.js27
-rw-r--r--app/assets/javascripts/task_list.js5
-rw-r--r--app/assets/javascripts/todos.js5
-rw-r--r--app/assets/javascripts/tree.js120
-rw-r--r--app/assets/javascripts/usage_ping.js5
-rw-r--r--app/assets/javascripts/user.js58
-rw-r--r--app/assets/javascripts/user_tabs.js184
-rw-r--r--app/assets/javascripts/username_validator.js214
-rw-r--r--app/assets/javascripts/users_select.js2
-rw-r--r--app/assets/javascripts/version_check_image.js3
-rw-r--r--app/assets/javascripts/visibility_select.js40
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_icon.vue15
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue18
-rw-r--r--app/assets/javascripts/webpack.js9
-rw-r--r--app/assets/javascripts/wikis.js95
-rw-r--r--app/assets/javascripts/zen_mode.js115
-rw-r--r--app/assets/stylesheets/framework/blank.scss66
-rw-r--r--app/assets/stylesheets/framework/buttons.scss18
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss71
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss17
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/modal.scss6
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss37
-rw-r--r--app/assets/stylesheets/framework/wells.scss14
-rw-r--r--app/assets/stylesheets/new_nav.scss206
-rw-r--r--app/assets/stylesheets/new_sidebar.scss209
-rw-r--r--app/assets/stylesheets/pages/builds.scss111
-rw-r--r--app/assets/stylesheets/pages/diff.scss10
-rw-r--r--app/assets/stylesheets/pages/environments.scss101
-rw-r--r--app/assets/stylesheets/pages/issuable.scss85
-rw-r--r--app/assets/stylesheets/pages/labels.scss6
-rw-r--r--app/assets/stylesheets/pages/members.scss112
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss10
-rw-r--r--app/assets/stylesheets/pages/note_form.scss51
-rw-r--r--app/assets/stylesheets/pages/notes.scss10
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss81
-rw-r--r--app/assets/stylesheets/pages/profile.scss3
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/assets/stylesheets/pages/runners.scss17
-rw-r--r--app/assets/stylesheets/pages/tree.scss9
-rw-r--r--app/assets/stylesheets/performance_bar.scss (renamed from vendor/assets/stylesheets/peek.scss)45
-rw-r--r--app/controllers/abuse_reports_controller.rb14
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/admin/projects_controller.rb4
-rw-r--r--app/controllers/application_controller.rb19
-rw-r--r--app/controllers/concerns/creates_commit.rb5
-rw-r--r--app/controllers/concerns/issuable_collections.rb36
-rw-r--r--app/controllers/concerns/membership_actions.rb2
-rw-r--r--app/controllers/concerns/milestone_actions.rb2
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb2
-rw-r--r--app/controllers/concerns/spammable_actions.rb10
-rw-r--r--app/controllers/concerns/with_performance_bar.rb17
-rw-r--r--app/controllers/dashboard/labels_controller.rb9
-rw-r--r--app/controllers/groups/milestones_controller.rb80
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb24
-rw-r--r--app/controllers/groups/variables_controller.rb64
-rw-r--r--app/controllers/invites_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb8
-rw-r--r--app/controllers/projects/application_controller.rb4
-rw-r--r--app/controllers/projects/artifacts_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb16
-rw-r--r--app/controllers/projects/branches_controller.rb11
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb10
-rw-r--r--app/controllers/projects/builds_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb6
-rw-r--r--app/controllers/projects/compare_controller.rb4
-rw-r--r--app/controllers/projects/environments_controller.rb8
-rw-r--r--app/controllers/projects/forks_controller.rb4
-rw-r--r--app/controllers/projects/graphs_controller.rb2
-rw-r--r--app/controllers/projects/group_links_controller.rb4
-rw-r--r--app/controllers/projects/hook_logs_controller.rb2
-rw-r--r--app/controllers/projects/hooks_controller.rb6
-rw-r--r--app/controllers/projects/imports_controller.rb12
-rw-r--r--app/controllers/projects/issues_controller.rb26
-rw-r--r--app/controllers/projects/jobs_controller.rb6
-rw-r--r--app/controllers/projects/labels_controller.rb17
-rw-r--r--app/controllers/projects/mattermosts_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb47
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb66
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb128
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb66
-rw-r--r--app/controllers/projects/merge_requests_controller.rb466
-rw-r--r--app/controllers/projects/milestones_controller.rb35
-rw-r--r--app/controllers/projects/network_controller.rb4
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/controllers/projects/pages_domains_controller.rb4
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb22
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb6
-rw-r--r--app/controllers/projects/project_members_controller.rb23
-rw-r--r--app/controllers/projects/refs_controller.rb18
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb4
-rw-r--r--app/controllers/projects/registry/tags_controller.rb4
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/controllers/projects/runners_controller.rb4
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb5
-rw-r--r--app/controllers/projects/settings/members_controller.rb27
-rw-r--r--app/controllers/projects/snippets_controller.rb8
-rw-r--r--app/controllers/projects/tags_controller.rb6
-rw-r--r--app/controllers/projects/tree_controller.rb6
-rw-r--r--app/controllers/projects/triggers_controller.rb10
-rw-r--r--app/controllers/projects/variables_controller.rb50
-rw-r--r--app/controllers/projects/wikis_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb4
-rw-r--r--app/finders/concerns/created_at_filter.rb8
-rw-r--r--app/finders/issuable_finder.rb49
-rw-r--r--app/finders/issues_finder.rb75
-rw-r--r--app/finders/labels_finder.rb7
-rw-r--r--app/finders/milestones_finder.rb60
-rw-r--r--app/finders/projects_finder.rb9
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/finders/users_finder.rb7
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/application_settings_helper.rb8
-rw-r--r--app/helpers/award_emoji_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb16
-rw-r--r--app/helpers/boards_helper.rb6
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/builds_helper.rb6
-rw-r--r--app/helpers/ci_status_helper.rb14
-rw-r--r--app/helpers/commits_helper.rb15
-rw-r--r--app/helpers/compare_helper.rb3
-rw-r--r--app/helpers/diff_helper.rb14
-rw-r--r--app/helpers/environment_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/events_helper.rb23
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/form_helper.rb16
-rw-r--r--app/helpers/gitlab_routing_helper.rb153
-rw-r--r--app/helpers/groups_helper.rb26
-rw-r--r--app/helpers/issuables_helper.rb101
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb10
-rw-r--r--app/helpers/merge_requests_helper.rb11
-rw-r--r--app/helpers/milestones_helper.rb28
-rw-r--r--app/helpers/nav_helper.rb7
-rw-r--r--app/helpers/notes_helper.rb25
-rw-r--r--app/helpers/performance_bar_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb44
-rw-r--r--app/helpers/search_helper.rb34
-rw-r--r--app/helpers/snippets_helper.rb7
-rw-r--r--app/helpers/submodule_helper.rb1
-rw-r--r--app/helpers/tab_helper.rb3
-rw-r--r--app/helpers/tags_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb2
-rw-r--r--app/helpers/webpack_helper.rb23
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/merge_requests.rb4
-rw-r--r--app/mailers/emails/notes.rb10
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/models/ability.rb74
-rw-r--r--app/models/appearance.rb2
-rw-r--r--app/models/application_setting.rb60
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/blob_viewer/readme.rb6
-rw-r--r--app/models/board.rb2
-rw-r--r--app/models/ci/build.rb36
-rw-r--r--app/models/ci/group_variable.rb13
-rw-r--r--app/models/ci/pipeline.rb26
-rw-r--r--app/models/ci/pipeline_schedule.rb8
-rw-r--r--app/models/ci/pipeline_schedule_variable.rb8
-rw-r--r--app/models/ci/runner.rb12
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb20
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/created_at_filterable.rb12
-rw-r--r--app/models/concerns/each_batch.rb81
-rw-r--r--app/models/concerns/feature_gate.rb7
-rw-r--r--app/models/concerns/has_variable.rb23
-rw-r--r--app/models/concerns/internal_id.rb3
-rw-r--r--app/models/concerns/issuable.rb15
-rw-r--r--app/models/concerns/mentionable/reference_regexes.rb2
-rw-r--r--app/models/concerns/milestoneish.rb16
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/routable.rb16
-rw-r--r--app/models/concerns/sha_attribute.rb20
-rw-r--r--app/models/concerns/sortable.rb23
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/concerns/subscribable.rb2
-rw-r--r--app/models/concerns/time_trackable.rb2
-rw-r--r--app/models/dashboard_milestone.rb4
-rw-r--r--app/models/deploy_key.rb2
-rw-r--r--app/models/diff_note.rb6
-rw-r--r--app/models/environment.rb5
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/external_issue.rb5
-rw-r--r--app/models/forked_project_link.rb4
-rw-r--r--app/models/global_label.rb2
-rw-r--r--app/models/global_milestone.rb47
-rw-r--r--app/models/group.rb26
-rw-r--r--app/models/group_milestone.rb4
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb6
-rw-r--r--app/models/issue.rb18
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/legacy_diff_note.rb2
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/merge_request.rb36
-rw-r--r--app/models/merge_request_diff.rb103
-rw-r--r--app/models/merge_request_diff_commit.rb38
-rw-r--r--app/models/milestone.rb74
-rw-r--r--app/models/namespace.rb12
-rw-r--r--app/models/note.rb7
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/project.rb249
-rw-r--r--app/models/project_feature.rb7
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb14
-rw-r--r--app/models/project_services/issue_tracker_service.rb7
-rw-r--r--app/models/project_services/jira_service.rb8
-rw-r--r--app/models/project_services/kubernetes_service.rb13
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_wiki.rb10
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/service.rb10
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/models/user.rb75
-rw-r--r--app/policies/base_policy.rb133
-rw-r--r--app/policies/ci/build_policy.rb28
-rw-r--r--app/policies/ci/pipeline_policy.rb4
-rw-r--r--app/policies/ci/pipeline_schedule_policy.rb10
-rw-r--r--app/policies/ci/runner_policy.rb15
-rw-r--r--app/policies/ci/trigger_policy.rb21
-rw-r--r--app/policies/commit_status_policy.rb6
-rw-r--r--app/policies/deploy_key_policy.rb14
-rw-r--r--app/policies/deployment_policy.rb4
-rw-r--r--app/policies/environment_policy.rb16
-rw-r--r--app/policies/external_issue_policy.rb4
-rw-r--r--app/policies/global_policy.rb60
-rw-r--r--app/policies/group_label_policy.rb4
-rw-r--r--app/policies/group_member_policy.rb29
-rw-r--r--app/policies/group_policy.rb98
-rw-r--r--app/policies/issuable_policy.rb19
-rw-r--r--app/policies/issue_policy.rb26
-rw-r--r--app/policies/namespace_policy.rb12
-rw-r--r--app/policies/nil_policy.rb3
-rw-r--r--app/policies/note_policy.rb31
-rw-r--r--app/policies/personal_snippet_policy.rb41
-rw-r--r--app/policies/project_label_policy.rb4
-rw-r--r--app/policies/project_member_policy.rb26
-rw-r--r--app/policies/project_policy.rb572
-rw-r--r--app/policies/project_snippet_policy.rb64
-rw-r--r--app/policies/user_policy.rb22
-rw-r--r--app/presenters/ci/group_variable_presenter.rb25
-rw-r--r--app/presenters/ci/variable_presenter.rb25
-rw-r--r--app/presenters/merge_request_presenter.rb27
-rw-r--r--app/serializers/build_action_entity.rb5
-rw-r--r--app/serializers/build_artifact_entity.rb15
-rw-r--r--app/serializers/build_details_entity.rb10
-rw-r--r--app/serializers/commit_entity.rb10
-rw-r--r--app/serializers/deployment_entity.rb5
-rw-r--r--app/serializers/environment_entity.rb20
-rw-r--r--app/serializers/issue_entity.rb2
-rw-r--r--app/serializers/label_entity.rb3
-rw-r--r--app/serializers/merge_request_entity.rb27
-rw-r--r--app/serializers/pipeline_entity.rb13
-rw-r--r--app/serializers/project_entity.rb2
-rw-r--r--app/serializers/runner_entity.rb2
-rw-r--r--app/serializers/stage_entity.rb6
-rw-r--r--app/services/access_token_validation_service.rb24
-rw-r--r--app/services/boards/create_service.rb19
-rw-r--r--app/services/boards/issues/list_service.rb12
-rw-r--r--app/services/chat_names/authorize_user_service.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb2
-rw-r--r--app/services/ci/register_job_service.rb4
-rw-r--r--app/services/delete_merged_branches_service.rb2
-rw-r--r--app/services/git_hooks_service.rb6
-rw-r--r--app/services/git_operation_service.rb2
-rw-r--r--app/services/groups/destroy_service.rb5
-rw-r--r--app/services/issuable_base_service.rb15
-rw-r--r--app/services/issues/move_service.rb14
-rw-r--r--app/services/merge_requests/get_urls_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/refresh_service.rb11
-rw-r--r--app/services/milestones/base_service.rb6
-rw-r--r--app/services/milestones/close_service.rb2
-rw-r--r--app/services/milestones/create_service.rb4
-rw-r--r--app/services/milestones/destroy_service.rb6
-rw-r--r--app/services/milestones/reopen_service.rb2
-rw-r--r--app/services/milestones/update_service.rb4
-rw-r--r--app/services/notification_recipient_service.rb17
-rw-r--r--app/services/projects/transfer_service.rb1
-rw-r--r--app/services/projects/unlink_fork_service.rb2
-rw-r--r--app/services/projects/update_pages_service.rb3
-rw-r--r--app/services/quick_actions/interpret_service.rb46
-rw-r--r--app/services/system_note_service.rb9
-rw-r--r--app/services/users/destroy_service.rb2
-rw-r--r--app/validators/variable_duplicates_validator.rb13
-rw-r--r--app/views/admin/application_settings/_form.html.haml20
-rw-r--r--app/views/admin/dashboard/index.html.haml322
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/projects/_projects.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml10
-rw-r--r--app/views/admin/runners/_runner.html.haml17
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/admin/users/projects.html.haml4
-rw-r--r--app/views/ci/variables/_content.html.haml (renamed from app/views/projects/variables/_content.html.haml)0
-rw-r--r--app/views/ci/variables/_form.html.haml (renamed from app/views/projects/variables/_form.html.haml)6
-rw-r--r--app/views/ci/variables/_index.html.haml (renamed from app/views/projects/variables/_index.html.haml)10
-rw-r--r--app/views/ci/variables/_show.html.haml9
-rw-r--r--app/views/ci/variables/_table.html.haml (renamed from app/views/projects/variables/_table.html.haml)6
-rw-r--r--app/views/dashboard/activity.html.haml1
-rw-r--r--app/views/dashboard/groups/index.html.haml1
-rw-r--r--app/views/dashboard/milestones/index.html.haml1
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml33
-rw-r--r--app/views/dashboard/projects/_blank_state_welcome.html.haml48
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml59
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/snippets/index.html.haml1
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml5
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/discussions/_new_issue_for_all_discussions.html.haml2
-rw-r--r--app/views/discussions/_new_issue_for_discussion.html.haml2
-rw-r--r--app/views/doorkeeper/applications/edit.html.haml1
-rw-r--r--app/views/doorkeeper/applications/index.html.haml5
-rw-r--r--app/views/doorkeeper/applications/show.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/groups/_settings_head.html.haml5
-rw-r--r--app/views/groups/edit.html.haml15
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/milestones/_form.html.haml27
-rw-r--r--app/views/groups/milestones/_milestone.html.haml3
-rw-r--r--app/views/groups/milestones/edit.html.haml7
-rw-r--r--app/views/groups/milestones/index.html.haml5
-rw-r--r--app/views/groups/milestones/new.html.haml38
-rw-r--r--app/views/groups/milestones/show.html.haml2
-rw-r--r--app/views/groups/projects.html.haml4
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml4
-rw-r--r--app/views/groups/variables/show.html.haml1
-rw-r--r--app/views/help/_shortcuts.html.haml9
-rw-r--r--app/views/import/base/create.js.haml2
-rw-r--r--app/views/invites/show.html.haml2
-rw-r--r--app/views/issues/_issue.atom.builder4
-rw-r--r--app/views/layouts/_head.html.haml9
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml12
-rw-r--r--app/views/layouts/_page.html.haml24
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/admin.html.haml6
-rw-r--r--app/views/layouts/application.html.haml3
-rw-r--r--app/views/layouts/group.html.haml6
-rw-r--r--app/views/layouts/header/_default.html.haml9
-rw-r--r--app/views/layouts/header/_new.html.haml4
-rw-r--r--app/views/layouts/header/_new_dropdown.haml6
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml19
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml123
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml61
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml53
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml247
-rw-r--r--app/views/layouts/nav/_project.html.haml37
-rw-r--r--app/views/layouts/profile.html.haml6
-rw-r--r--app/views/layouts/project.html.haml8
-rw-r--r--app/views/notify/closed_issue_email.text.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/issue_moved_email.html.haml2
-rw-r--r--app/views/notify/issue_moved_email.text.erb2
-rw-r--r--app/views/notify/issue_status_changed_email.text.erb2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.text.erb2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/notify/project_was_exported_email.html.haml2
-rw-r--r--app/views/notify/project_was_exported_email.text.erb2
-rw-r--r--app/views/notify/project_was_moved_email.html.haml2
-rw-r--r--app/views/notify/project_was_moved_email.text.erb2
-rw-r--r--app/views/notify/repository_push_email.html.haml4
-rw-r--r--app/views/notify/resolved_all_discussions_email.text.erb2
-rw-r--r--app/views/peek/views/_rblineprof.html.haml7
-rw-r--r--app/views/peek/views/_sql.html.haml8
-rw-r--r--app/views/profiles/accounts/show.html.haml21
-rw-r--r--app/views/profiles/audit_log.html.haml5
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml2
-rw-r--r--app/views/profiles/chat_names/index.html.haml5
-rw-r--r--app/views/profiles/emails/index.html.haml5
-rw-r--r--app/views/profiles/keys/index.html.haml5
-rw-r--r--app/views/profiles/keys/show.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml5
-rw-r--r--app/views/profiles/passwords/edit.html.haml5
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml5
-rw-r--r--app/views/profiles/preferences/show.html.haml44
-rw-r--r--app/views/profiles/show.html.haml9
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml13
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/_find_file_link.html.haml2
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/_wiki.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_directory.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml6
-rw-r--r--app/views/projects/artifacts/file.html.haml6
-rw-r--r--app/views/projects/blame/show.html.haml4
-rw-r--r--app/views/projects/blob/_breadcrumb.html.haml14
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_remove.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml8
-rw-r--r--app/views/projects/blob/new.html.haml4
-rw-r--r--app/views/projects/blob/show.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_changelog.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_readme.html.haml2
-rw-r--r--app/views/projects/boards/_show.html.haml6
-rw-r--r--app/views/projects/boards/components/_sidebar.html.haml3
-rw-r--r--app/views/projects/boards/components/sidebar/_assignee.html.haml7
-rw-r--r--app/views/projects/boards/components/sidebar/_due_date.html.haml2
-rw-r--r--app/views/projects/boards/components/sidebar/_labels.html.haml4
-rw-r--r--app/views/projects/boards/components/sidebar/_milestone.html.haml4
-rw-r--r--app/views/projects/boards/components/sidebar/_notifications.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml8
-rw-r--r--app/views/projects/branches/_commit.html.haml4
-rw-r--r--app/views/projects/branches/index.html.haml6
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/buttons/_download.html.haml10
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml18
-rw-r--r--app/views/projects/buttons/_fork.html.haml6
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml14
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml22
-rw-r--r--app/views/projects/commit/pipelines.html.haml2
-rw-r--r--app/views/projects/commits/_commit.atom.builder4
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/commits/_commits.html.haml4
-rw-r--r--app/views/projects/commits/_head.html.haml16
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml4
-rw-r--r--app/views/projects/commits/show.atom.builder6
-rw-r--r--app/views/projects/commits/show.html.haml39
-rw-r--r--app/views/projects/compare/_form.html.haml8
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/deploy_keys/edit.html.haml2
-rw-r--r--app/views/projects/deployments/_commit.html.haml4
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml3
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml7
-rw-r--r--app/views/projects/diffs/_warning.html.haml13
-rw-r--r--app/views/projects/diffs/viewers/_image.html.haml4
-rw-r--r--app/views/projects/edit.html.haml20
-rw-r--r--app/views/projects/empty.html.haml3
-rw-r--r--app/views/projects/environments/_form.html.haml2
-rw-r--r--app/views/projects/environments/_stop.html.haml2
-rw-r--r--app/views/projects/environments/_terminal_button.html.haml2
-rw-r--r--app/views/projects/environments/index.html.haml2
-rw-r--r--app/views/projects/environments/metrics.html.haml77
-rw-r--r--app/views/projects/environments/show.html.haml4
-rw-r--r--app/views/projects/environments/terminal.html.haml2
-rw-r--r--app/views/projects/find_file/show.html.haml8
-rw-r--r--app/views/projects/forks/error.html.haml2
-rw-r--r--app/views/projects/forks/index.html.haml2
-rw-r--r--app/views/projects/forks/new.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml6
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/hook_logs/_index.html.haml2
-rw-r--r--app/views/projects/hook_logs/show.html.haml2
-rw-r--r--app/views/projects/hooks/edit.html.haml7
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_head.html.haml10
-rw-r--r--app/views/projects/issues/_issue.html.haml66
-rw-r--r--app/views/projects/issues/_issue_by_email.html.haml2
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml10
-rw-r--r--app/views/projects/issues/_new_branch.html.haml2
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder4
-rw-r--r--app/views/projects/issues/index.html.haml21
-rw-r--r--app/views/projects/issues/show.html.haml33
-rw-r--r--app/views/projects/jobs/_header.html.haml8
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml14
-rw-r--r--app/views/projects/jobs/index.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml21
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml4
-rw-r--r--app/views/projects/labels/new.html.haml2
-rw-r--r--app/views/projects/mattermosts/_no_teams.html.haml2
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml4
-rw-r--r--app/views/projects/merge_requests/_commits.html.haml (renamed from app/views/projects/merge_requests/show/_commits.html.haml)0
-rw-r--r--app/views/projects/merge_requests/_head.html.haml6
-rw-r--r--app/views/projects/merge_requests/_how_to_merge.html.haml (renamed from app/views/projects/merge_requests/show/_how_to_merge.html.haml)0
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml76
-rw-r--r--app/views/projects/merge_requests/_mr_box.html.haml (renamed from app/views/projects/merge_requests/show/_mr_box.html.haml)0
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml40
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml5
-rw-r--r--app/views/projects/merge_requests/_pipelines.html.haml (renamed from app/views/projects/merge_requests/show/_pipelines.html.haml)2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml97
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml8
-rw-r--r--app/views/projects/merge_requests/conflicts/_submit_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml38
-rw-r--r--app/views/projects/merge_requests/creations/_diffs.html.haml (renamed from app/views/projects/merge_requests/_new_diffs.html.haml)0
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml (renamed from app/views/projects/merge_requests/_new_compare.html.haml)8
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml (renamed from app/views/projects/merge_requests/_new_submit.html.haml)11
-rw-r--r--app/views/projects/merge_requests/creations/branch_from.html.haml (renamed from app/views/projects/merge_requests/branch_from.html.haml)0
-rw-r--r--app/views/projects/merge_requests/creations/branch_to.html.haml (renamed from app/views/projects/merge_requests/branch_to.html.haml)0
-rw-r--r--app/views/projects/merge_requests/creations/new.html.haml (renamed from app/views/projects/merge_requests/new.html.haml)0
-rw-r--r--app/views/projects/merge_requests/creations/update_branches.html.haml (renamed from app/views/projects/merge_requests/update_branches.html.haml)0
-rw-r--r--app/views/projects/merge_requests/diffs.html.haml1
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml (renamed from app/views/projects/merge_requests/show/_diffs.html.haml)2
-rw-r--r--app/views/projects/merge_requests/diffs/_versions.html.haml (renamed from app/views/projects/merge_requests/show/_versions.html.haml)4
-rw-r--r--app/views/projects/merge_requests/index.html.haml16
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml4
-rw-r--r--app/views/projects/merge_requests/show.html.haml98
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml35
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/milestones/_milestone.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml8
-rw-r--r--app/views/projects/network/show.html.haml2
-rw-r--r--app/views/projects/no_repo.html.haml4
-rw-r--r--app/views/projects/pages/_destroy.haml2
-rw-r--r--app/views/projects/pages/_list.html.haml4
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml8
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml4
-rw-r--r--app/views/projects/pipeline_schedules/_variable_row.html.haml17
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml2
-rw-r--r--app/views/projects/pipelines/_head.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml6
-rw-r--r--app/views/projects/pipelines/charts.html.haml4
-rw-r--r--app/views/projects/pipelines/charts/_overall.haml16
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_times.haml2
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml12
-rw-r--r--app/views/projects/pipelines/index.html.haml4
-rw-r--r--app/views/projects/pipelines/new.html.haml4
-rw-r--r--app/views/projects/pipelines/show.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml10
-rw-r--r--app/views/projects/project_members/_group_members.html.haml18
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_new_shared_group.html.haml2
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml24
-rw-r--r--app/views/projects/project_members/_team.html.haml4
-rw-r--r--app/views/projects/project_members/import.html.haml4
-rw-r--r--app/views/projects/project_members/index.html.haml (renamed from app/views/projects/project_members/_index.html.haml)7
-rw-r--r--app/views/projects/protected_branches/_matching_branch.html.haml2
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml6
-rw-r--r--app/views/projects/protected_tags/_matching_tag.html.haml2
-rw-r--r--app/views/projects/protected_tags/_protected_tag.html.haml6
-rw-r--r--app/views/projects/registry/repositories/_image.html.haml3
-rw-r--r--app/views/projects/registry/repositories/_tag.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml4
-rw-r--r--app/views/projects/remove_fork.js.haml2
-rw-r--r--app/views/projects/repositories/_feed.html.haml4
-rw-r--r--app/views/projects/runners/_runner.html.haml4
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml25
-rw-r--r--app/views/projects/services/_index.html.haml2
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml2
-rw-r--r--app/views/projects/settings/_head.html.haml10
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml6
-rw-r--r--app/views/projects/show.atom.builder6
-rw-r--r--app/views/projects/show.html.haml17
-rw-r--r--app/views/projects/snippets/_actions.html.haml16
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml4
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml6
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/transfer.js.haml2
-rw-r--r--app/views/projects/tree/_blob_item.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml4
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml4
-rw-r--r--app/views/projects/tree/_tree_header.html.haml20
-rw-r--r--app/views/projects/tree/_tree_item.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml7
-rw-r--r--app/views/projects/triggers/_trigger.html.haml6
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/variables/show.html.haml10
-rw-r--r--app/views/projects/wikis/_form.html.haml6
-rw-r--r--app/views/projects/wikis/_main_links.html.haml4
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml4
-rw-r--r--app/views/projects/wikis/_sidebar_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml6
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml8
-rw-r--r--app/views/shared/_issues.html.haml2
-rw-r--r--app/views/shared/_label.html.haml6
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_merge_requests.html.haml2
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml2
-rw-r--r--app/views/shared/_ref_switcher.html.haml4
-rw-r--r--app/views/shared/_service_settings.html.haml9
-rw-r--r--app/views/shared/_sort_dropdown.html.haml4
-rw-r--r--app/views/shared/empty_states/_labels.html.haml4
-rw-r--r--app/views/shared/icons/_add_new_group.svg8
-rw-r--r--app/views/shared/icons/_add_new_project.svg1
-rw-r--r--app/views/shared/icons/_add_new_user.svg9
-rw-r--r--app/views/shared/icons/_configure_server.svg8
-rw-r--r--app/views/shared/icons/_globe.svg1
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml14
-rw-r--r--app/views/shared/issuable/_close_reopen_report_toggle.html.haml49
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml4
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml9
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml3
-rw-r--r--app/views/shared/issuable/form/_metadata_issue_assignee.html.haml2
-rw-r--r--app/views/shared/members/_access_request_buttons.html.haml10
-rw-r--r--app/views/shared/members/_group.html.haml4
-rw-r--r--app/views/shared/members/_member.html.haml80
-rw-r--r--app/views/shared/members/_requests.html.haml8
-rw-r--r--app/views/shared/milestones/_issuable.html.haml11
-rw-r--r--app/views/shared/milestones/_milestone.html.haml41
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml6
-rw-r--r--app/views/shared/milestones/_tabs.html.haml11
-rw-r--r--app/views/shared/milestones/_top.html.haml64
-rw-r--r--app/views/shared/notes/_comment_button.html.haml10
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/shared/snippets/_snippet.html.haml2
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/workers/background_migration_worker.rb20
-rw-r--r--app/workers/expire_job_cache_worker.rb12
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb28
-rw-r--r--app/workers/post_receive.rb21
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
-rw-r--r--changelogs/unreleased/18000-remember-me-for-oauth-login.yml4
-rw-r--r--changelogs/unreleased/20628-add-oauth-implicit-grant.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-snippets-spinach.yml4
-rw-r--r--changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml4
-rw-r--r--changelogs/unreleased/2501-ce-port-update-welcome-page.yml4
-rw-r--r--changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml4
-rw-r--r--changelogs/unreleased/26125-match-username-on-search.yml5
-rw-r--r--changelogs/unreleased/29893-change-menu-locations.yml3
-rw-r--r--changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml4
-rw-r--r--changelogs/unreleased/31982-liberation-mono-linux.yml4
-rw-r--r--changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml4
-rw-r--r--changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml4
-rw-r--r--changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml4
-rw-r--r--changelogs/unreleased/32838-admin-panel-spacing.yml4
-rw-r--r--changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml4
-rw-r--r--changelogs/unreleased/33130-remove-group-modal.yml4
-rw-r--r--changelogs/unreleased/33360-generate-kubeconfig.yml4
-rw-r--r--changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33580-fix-api-scoping.yml4
-rw-r--r--changelogs/unreleased/33657-user-projects-api.yml4
-rw-r--r--changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml4
-rw-r--r--changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml4
-rw-r--r--changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml4
-rw-r--r--changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml4
-rw-r--r--changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml4
-rw-r--r--changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml4
-rw-r--r--changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34282-fix-api-using-include_missing-false.yml4
-rw-r--r--changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml4
-rw-r--r--changelogs/unreleased/34531-remove-scroll.yml4
-rw-r--r--changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml4
-rw-r--r--changelogs/unreleased/34578-sidebar-padding.yml4
-rw-r--r--changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml4
-rw-r--r--changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml4
-rw-r--r--changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml4
-rw-r--r--changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34727-simplified-member-settings.yml4
-rw-r--r--changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml4
-rw-r--r--changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml4
-rw-r--r--changelogs/unreleased/34810-vue-pagination.yml4
-rw-r--r--changelogs/unreleased/adam-external-issue-references-spike.yml4
-rw-r--r--changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml6
-rw-r--r--changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml4
-rw-r--r--changelogs/unreleased/bvl-rename-all-reserved-paths.yml4
-rw-r--r--changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml4
-rw-r--r--changelogs/unreleased/dm-empty-state-new-merge-request.yml5
-rw-r--r--changelogs/unreleased/dm-encode-tree-and-blob-paths.yml5
-rw-r--r--changelogs/unreleased/dm-page-image-size.yml4
-rw-r--r--changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml4
-rw-r--r--changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml4
-rw-r--r--changelogs/unreleased/enable-polling-env.yml4
-rw-r--r--changelogs/unreleased/enable-webpack-code-splitting.yml5
-rw-r--r--changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml4
-rw-r--r--changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml4
-rw-r--r--changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml4
-rw-r--r--changelogs/unreleased/feature-user-agent-details-api.yml4
-rw-r--r--changelogs/unreleased/feature-user-datetime-search-api-mysql.yml4
-rw-r--r--changelogs/unreleased/fix-assigned-issuable-lists.yml5
-rw-r--r--changelogs/unreleased/fix-head-pipeline-for-commit-status.yml4
-rw-r--r--changelogs/unreleased/fix-mrs-merged-immediately.yml4
-rw-r--r--changelogs/unreleased/fix-runner_online_check.yml4
-rw-r--r--changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml4
-rw-r--r--changelogs/unreleased/foreign-keys-for-project-model.yml4
-rw-r--r--changelogs/unreleased/gitaly-mandatory.yml4
-rw-r--r--changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml4
-rw-r--r--changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/issue-description-gfm.yml4
-rw-r--r--changelogs/unreleased/issue-form-multiple-line-markdown.yml4
-rw-r--r--changelogs/unreleased/issue_30126_be.yml4
-rw-r--r--changelogs/unreleased/issueable-list-cleanup.yml4
-rw-r--r--changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml4
-rw-r--r--changelogs/unreleased/monitoring-dashboard-fix-y-label.yml4
-rw-r--r--changelogs/unreleased/sh-allow-force-repo-create.yml4
-rw-r--r--changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml5
-rw-r--r--changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml4
-rw-r--r--changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml4
-rw-r--r--changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml4
-rw-r--r--changelogs/unreleased/sh-optimize-project-commit-api.yml4
-rw-r--r--changelogs/unreleased/speed-up-issue-counting-for-a-project.yml5
-rw-r--r--changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml4
-rw-r--r--changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml5
-rw-r--r--changelogs/unreleased/tc-follow-up-mia.yml4
-rw-r--r--changelogs/unreleased/update_bootsnap_1-1-1.yml4
-rw-r--r--changelogs/unreleased/username-password-stripped-from-import-url-fix.yml4
-rw-r--r--changelogs/unreleased/workhorse-2-3-0.yml4
-rw-r--r--changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml4
-rw-r--r--config/application.rb24
-rw-r--r--config/boot.rb6
-rw-r--r--config/gitlab.yml.example54
-rw-r--r--config/initializers/1_settings.rb7
-rw-r--r--config/initializers/5_backend.rb10
-rw-r--r--config/initializers/8_gitaly.rb8
-rw-r--r--config/initializers/8_metrics.rb9
-rw-r--r--config/initializers/doorkeeper.rb4
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb2
-rw-r--r--config/initializers/flipper.rb8
-rw-r--r--config/initializers/relative_naming_ci_namespace.rb4
-rw-r--r--config/prometheus/additional_metrics.yml86
-rw-r--r--config/routes/group.rb10
-rw-r--r--config/routes/legacy_builds.rb22
-rw-r--r--config/routes/project.rb61
-rw-r--r--config/webpack.config.js7
-rw-r--r--db/migrate/20160804142904_add_ci_config_file_to_project.rb11
-rw-r--r--db/migrate/20170525130346_create_group_variables_table.rb23
-rw-r--r--db/migrate/20170525130758_add_foreign_key_to_group_variables.rb15
-rw-r--r--db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb187
-rw-r--r--db/migrate/20170616133147_create_merge_request_diff_commits.rb20
-rw-r--r--db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb25
-rw-r--r--db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb15
-rw-r--r--db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb40
-rw-r--r--db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb30
-rw-r--r--db/migrate/20170622135451_rename_duplicated_variable_key.rb38
-rw-r--r--db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb15
-rw-r--r--db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb38
-rw-r--r--db/migrate/20170623080805_remove_ci_variables_project_id_index.rb19
-rw-r--r--db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb35
-rw-r--r--db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb9
-rw-r--r--db/migrate/20170707183807_add_group_id_to_milestones.rb20
-rw-r--r--db/migrate/20170707184243_add_group_milestone_id_indexes.rb21
-rw-r--r--db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb10
-rw-r--r--db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb113
-rw-r--r--db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb14
-rw-r--r--db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb33
-rw-r--r--db/schema.rb96
-rw-r--r--doc/README.md30
-rw-r--r--doc/administration/gitaly/index.md41
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin0 -> 186116 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_configuration_settings.pngbin0 -> 20385 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_line_profiling.pngbin0 -> 161313 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_sql_queries.pngbin0 -> 165124 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md35
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/features.md7
-rw-r--r--doc/api/issues.md27
-rw-r--r--doc/api/namespaces.md14
-rw-r--r--doc/api/oauth2.md116
-rw-r--r--doc/api/project_snippets.md32
-rw-r--r--doc/api/projects.md163
-rw-r--r--doc/api/repository_files.md6
-rw-r--r--doc/api/snippets.md32
-rw-r--r--doc/api/users.md6
-rw-r--r--doc/ci/docker/using_docker_images.md322
-rw-r--r--doc/ci/ssh_keys/README.md6
-rw-r--r--doc/ci/variables/README.md43
-rw-r--r--doc/ci/yaml/README.md47
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/background_migrations.md17
-rw-r--r--doc/development/feature_flags.md18
-rw-r--r--doc/development/iterating_tables_in_batches.md37
-rw-r--r--doc/development/policies.md116
-rw-r--r--doc/development/sha1_as_binary.md36
-rw-r--r--doc/downgrade_ee_to_ce/README.md13
-rw-r--r--doc/install/database_mysql.md15
-rw-r--r--doc/install/installation.md10
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md53
-rw-r--r--doc/integration/external-issue-tracker.md3
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--doc/update/9.1-to-9.2.md41
-rw-r--r--doc/update/9.2-to-9.3.md45
-rw-r--r--doc/update/9.3-to-9.4.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md3
-rw-r--r--doc/user/project/img/issue_board.pngbin76461 -> 51439 bytes
-rw-r--r--doc/user/project/img/issue_board_add_list.pngbin23632 -> 17312 bytes
-rw-r--r--doc/user/project/img/issue_board_move_issue_card_list.pngbin0 -> 74826 bytes
-rw-r--r--doc/user/project/img/issue_board_welcome_message.pngbin120751 -> 26533 bytes
-rw-r--r--doc/user/project/img/issue_boards_add_issues_modal.pngbin177057 -> 29176 bytes
-rw-r--r--doc/user/project/integrations/bugzilla.md11
-rw-r--r--doc/user/project/integrations/kubernetes.md1
-rw-r--r--doc/user/project/integrations/redmine.md11
-rw-r--r--doc/user/project/issue_board.md70
-rw-r--r--doc/user/project/issues/index.md2
-rw-r--r--doc/user/project/milestones/index.md13
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedule_variables.pngbin0 -> 13478 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_new_form.pngbin49873 -> 72501 bytes
-rw-r--r--doc/user/project/pipelines/schedules.md10
-rw-r--r--doc/user/project/pipelines/settings.md21
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--features/dashboard/new_project.feature30
-rw-r--r--features/group/milestones.feature4
-rw-r--r--features/project/active_tab.feature11
-rw-r--r--features/project/source/browse_files.feature10
-rw-r--r--features/snippets/snippets.feature40
-rw-r--r--features/steps/dashboard/dashboard.rb2
-rw-r--r--features/steps/dashboard/new_project.rb59
-rw-r--r--features/steps/dashboard/starred_projects.rb15
-rw-r--r--features/steps/explore/projects.rb8
-rw-r--r--features/steps/group/milestones.rb11
-rw-r--r--features/steps/project/archived.rb2
-rw-r--r--features/steps/project/badges/build.rb2
-rw-r--r--features/steps/project/commits/commits.rb8
-rw-r--r--features/steps/project/commits/revert.rb2
-rw-r--r--features/steps/project/commits/user_lookup.rb4
-rw-r--r--features/steps/project/create.rb4
-rw-r--r--features/steps/project/deploy_keys.rb2
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb6
-rw-r--r--features/steps/project/graph.rb10
-rw-r--r--features/steps/project/issues/award_emoji.rb2
-rw-r--r--features/steps/project/issues/issues.rb6
-rw-r--r--features/steps/project/issues/labels.rb2
-rw-r--r--features/steps/project/merge_requests.rb4
-rw-r--r--features/steps/project/merge_requests/acceptance.rb1
-rw-r--r--features/steps/project/merge_requests/revert.rb1
-rw-r--r--features/steps/project/network_graph.rb4
-rw-r--r--features/steps/project/pages.rb4
-rw-r--r--features/steps/project/project_group_links.rb2
-rw-r--r--features/steps/project/redirects.rb6
-rw-r--r--features/steps/project/services.rb2
-rw-r--r--features/steps/project/snippets.rb2
-rw-r--r--features/steps/project/source/browse_files.rb32
-rw-r--r--features/steps/project/source/markdown_render.rb72
-rw-r--r--features/steps/project/wiki.rb8
-rw-r--r--features/steps/shared/builds.rb6
-rw-r--r--features/steps/shared/diff_note.rb2
-rw-r--r--features/steps/shared/issuable.rb8
-rw-r--r--features/steps/shared/paths.rb108
-rw-r--r--features/steps/shared/project.rb6
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--features/steps/shared/snippet.rb63
-rw-r--r--features/steps/snippets/snippets.rb86
-rw-r--r--features/support/env.rb1
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/api_guard.rb33
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb62
-rw-r--r--lib/api/features.rb44
-rw-r--r--lib/api/helpers.rb24
-rw-r--r--lib/api/helpers/internal_helpers.rb35
-rw-r--r--lib/api/helpers/runner.rb3
-rw-r--r--lib/api/internal.rb3
-rw-r--r--lib/api/issues.rb16
-rw-r--r--lib/api/merge_requests.rb8
-rw-r--r--lib/api/namespaces.rb2
-rw-r--r--lib/api/pipeline_schedules.rb9
-rw-r--r--lib/api/project_snippets.rb16
-rw-r--r--lib/api/projects.rb124
-rw-r--r--lib/api/scope.rb23
-rw-r--r--lib/api/snippets.rb16
-rw-r--r--lib/api/users.rb37
-rw-r--r--lib/api/v3/users.rb4
-rw-r--r--lib/api/variables.rb8
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb15
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb2
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb4
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb32
-rw-r--r--lib/banzai/filter/label_reference_filter.rb3
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
-rw-r--r--lib/banzai/filter/reference_filter.rb2
-rw-r--r--lib/banzai/filter/snippet_reference_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb2
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb3
-rw-r--r--lib/ci/api/helpers.rb3
-rw-r--r--lib/declarative_policy.rb58
-rw-r--r--lib/declarative_policy/base.rb329
-rw-r--r--lib/declarative_policy/cache.rb32
-rw-r--r--lib/declarative_policy/condition.rb102
-rw-r--r--lib/declarative_policy/dsl.rb103
-rw-r--r--lib/declarative_policy/preferred_scope.rb28
-rw-r--r--lib/declarative_policy/rule.rb301
-rw-r--r--lib/declarative_policy/runner.rb181
-rw-r--r--lib/declarative_policy/step.rb86
-rw-r--r--lib/extracts_path.rb3
-rw-r--r--lib/feature.rb28
-rw-r--r--lib/gitlab/allowable.rb4
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/background_migration/migrate_build_stage_id_reference.rb19
-rw-r--r--lib/gitlab/badge/build/metadata.rb5
-rw-r--r--lib/gitlab/badge/coverage/metadata.rb6
-rw-r--r--lib/gitlab/badge/metadata.rb2
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb14
-rw-r--r--lib/gitlab/ci/config/entry/image.rb2
-rw-r--r--lib/gitlab/ci/config/entry/service.rb4
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb4
-rw-r--r--lib/gitlab/ci/status/build/common.rb4
-rw-r--r--lib/gitlab/ci/status/build/play.rb4
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb4
-rw-r--r--lib/gitlab/ci/status/build/stop.rb4
-rw-r--r--lib/gitlab/ci/status/pipeline/common.rb4
-rw-r--r--lib/gitlab/ci/status/stage/common.rb5
-rw-r--r--lib/gitlab/conflict/file.rb15
-rw-r--r--lib/gitlab/current_settings.rb7
-rw-r--r--lib/gitlab/cycle_analytics/metrics_tables.rb4
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb37
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1.rb5
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb106
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb23
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb25
-rw-r--r--lib/gitlab/database/sha_attribute.rb34
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb2
-rw-r--r--lib/gitlab/ee_compat_check.rb2
-rw-r--r--lib/gitlab/email/message/repository_push.rb15
-rw-r--r--lib/gitlab/git.rb4
-rw-r--r--lib/gitlab/git/blob.rb49
-rw-r--r--lib/gitlab/git/hook.rb8
-rw-r--r--lib/gitlab/git/index.rb4
-rw-r--r--lib/gitlab/git/repository.rb84
-rw-r--r--lib/gitlab/git/tree.rb4
-rw-r--r--lib/gitlab/gitaly_client.rb6
-rw-r--r--lib/gitlab/gitaly_client/blob.rb30
-rw-r--r--lib/gitlab/gitaly_client/commit.rb9
-rw-r--r--lib/gitlab/gitaly_client/ref.rb27
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb2
-rw-r--r--lib/gitlab/i18n.rb3
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/issuable_metadata.rb36
-rw-r--r--lib/gitlab/kubernetes.rb39
-rw-r--r--lib/gitlab/ldap/access.rb2
-rw-r--r--lib/gitlab/metrics/base_sampler.rb94
-rw-r--r--lib/gitlab/metrics/connection_rack_middleware.rb45
-rw-r--r--lib/gitlab/metrics/influx_sampler.rb (renamed from lib/gitlab/metrics/sampler.rb)40
-rw-r--r--lib/gitlab/metrics/prometheus.rb4
-rw-r--r--lib/gitlab/metrics/unicorn_sampler.rb48
-rw-r--r--lib/gitlab/otp_key_rotator.rb2
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/performance_bar.rb30
-rw-r--r--lib/gitlab/regex.rb14
-rw-r--r--lib/gitlab/routes/legacy_builds.rb36
-rw-r--r--lib/gitlab/routing.rb25
-rw-r--r--lib/gitlab/shell.rb69
-rw-r--r--lib/gitlab/slash_commands/presenters/base.rb2
-rw-r--r--lib/gitlab/sql/glob.rb22
-rw-r--r--lib/gitlab/url_builder.rb10
-rw-r--r--lib/gitlab/usage_data.rb3
-rw-r--r--lib/gitlab/view/presenter/base.rb5
-rw-r--r--lib/gitlab/workhorse.rb40
-rw-r--r--lib/peek/rblineprof/custom_controller_helpers.rb32
-rw-r--r--lib/tasks/gitlab/info.rake3
-rw-r--r--locale/bg/gitlab.po98
-rw-r--r--locale/en/gitlab.po115
-rw-r--r--locale/eo/gitlab.po46
-rw-r--r--locale/es/gitlab.po4
-rw-r--r--locale/gitlab.pot79
-rw-r--r--locale/it/gitlab.po1185
-rw-r--r--locale/it/gitlab.po.time_stamp0
-rw-r--r--locale/zh_CN/gitlab.po42
-rw-r--r--locale/zh_HK/gitlab.po40
-rw-r--r--locale/zh_TW/gitlab.po1001
-rw-r--r--package.json1
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/main/menu.rb7
-rw-r--r--qa/qa/page/main/projects.rb16
-rw-r--r--qa/qa/scenario/gitlab/project/create.rb3
-rw-r--r--qa/qa/specs/config.rb18
-rw-r--r--rubocop/cop/active_record_dependent.rb26
-rw-r--r--rubocop/cop/active_record_serialize.rb (renamed from rubocop/cop/activerecord_serialize.rb)2
-rw-r--r--rubocop/cop/in_batches.rb16
-rw-r--r--rubocop/cop/project_path_helper.rb51
-rw-r--r--rubocop/rubocop.rb5
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb25
-rw-r--r--spec/controllers/dashboard/labels_controller_spec.rb25
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb123
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb20
-rw-r--r--spec/controllers/groups/variables_controller_spec.rb56
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb11
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb11
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb8
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb8
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb10
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb6
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb10
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb4
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb4
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb307
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb120
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb160
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb608
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb34
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb327
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb19
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/members_controller_spec.rb14
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb9
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb4
-rw-r--r--spec/controllers/snippets_controller_spec.rb2
-rw-r--r--spec/factories/ci/builds.rb3
-rw-r--r--spec/factories/ci/group_variables.rb12
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb8
-rw-r--r--spec/factories/ci/runner_projects.rb4
-rw-r--r--spec/factories/milestones.rb22
-rw-r--r--spec/factories/personal_snippets.rb4
-rw-r--r--spec/factories/project_snippets.rb5
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/factories/snippets.rb7
-rw-r--r--spec/features/abuse_report_spec.rb4
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb2
-rw-r--r--spec/features/admin/admin_active_tab_spec.rb2
-rw-r--r--spec/features/admin/admin_appearance_spec.rb8
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb2
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_builds_spec.rb2
-rw-r--r--spec/features/admin/admin_cohorts_spec.rb2
-rw-r--r--spec/features/admin/admin_conversational_development_index_spec.rb2
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb4
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb4
-rw-r--r--spec/features/admin/admin_groups_spec.rb3
-rw-r--r--spec/features/admin/admin_health_check_spec.rb2
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/admin/admin_labels_spec.rb2
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb14
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb2
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb15
-rw-r--r--spec/features/admin/admin_system_info_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb6
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb4
-rw-r--r--spec/features/atom/issues_spec.rb10
-rw-r--r--spec/features/auto_deploy_spec.rb6
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb6
-rw-r--r--spec/features/boards/boards_spec.rb20
-rw-r--r--spec/features/boards/issue_ordering_spec.rb8
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb4
-rw-r--r--spec/features/boards/modal_filter_spec.rb4
-rw-r--r--spec/features/boards/new_issue_spec.rb6
-rw-r--r--spec/features/boards/sidebar_spec.rb20
-rw-r--r--spec/features/boards/sub_group_project_spec.rb4
-rw-r--r--spec/features/calendar_spec.rb2
-rw-r--r--spec/features/ci_lint_spec.rb2
-rw-r--r--spec/features/commits_spec.rb4
-rw-r--r--spec/features/container_registry_spec.rb5
-rw-r--r--spec/features/copy_as_gfm_spec.rb18
-rw-r--r--spec/features/cycle_analytics_spec.rb16
-rw-r--r--spec/features/dashboard/active_tab_spec.rb2
-rw-r--r--spec/features/dashboard/activity_spec.rb4
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb2
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb4
-rw-r--r--spec/features/dashboard/group_spec.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb8
-rw-r--r--spec/features/dashboard/help_spec.rb2
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb2
-rw-r--r--spec/features/dashboard/issues_spec.rb3
-rw-r--r--spec/features/dashboard/label_filter_spec.rb2
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb2
-rw-r--r--spec/features/dashboard/milestone_tabs_spec.rb2
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb28
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb2
-rw-r--r--spec/features/dashboard/snippets_spec.rb4
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb19
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb2
-rw-r--r--spec/features/dashboard_issues_spec.rb2
-rw-r--r--spec/features/dashboard_milestones_spec.rb2
-rw-r--r--spec/features/discussion_comments/commit_spec.rb4
-rw-r--r--spec/features/discussion_comments/issue_spec.rb4
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb4
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb4
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb10
-rw-r--r--spec/features/explore/groups_list_spec.rb2
-rw-r--r--spec/features/explore/new_menu_spec.rb22
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb24
-rw-r--r--spec/features/global_search_spec.rb2
-rw-r--r--spec/features/group_variables_spec.rb78
-rw-r--r--spec/features/groups/activity_spec.rb4
-rw-r--r--spec/features/groups/empty_states_spec.rb2
-rw-r--r--spec/features/groups/group_name_toggle_spec.rb2
-rw-r--r--spec/features/groups/group_settings_spec.rb2
-rw-r--r--spec/features/groups/labels/edit_spec.rb2
-rw-r--r--spec/features/groups/members/manage_access_requests_spec.rb2
-rw-r--r--spec/features/groups/members/manage_members.rb2
-rw-r--r--spec/features/groups/members/request_access_spec.rb2
-rw-r--r--spec/features/groups/members/sort_members_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb30
-rw-r--r--spec/features/groups/show_spec.rb7
-rw-r--r--spec/features/groups_spec.rb22
-rw-r--r--spec/features/help_pages_spec.rb4
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb116
-rw-r--r--spec/features/issuables/issuable_list_spec.rb6
-rw-r--r--spec/features/issuables/user_sees_sidebar_spec.rb30
-rw-r--r--spec/features/issues/award_emoji_spec.rb8
-rw-r--r--spec/features/issues/award_spec.rb10
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb8
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb18
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb22
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb17
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb18
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb6
-rw-r--r--spec/features/issues/form_spec.rb19
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb16
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb8
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb18
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb4
-rw-r--r--spec/features/issues/move_spec.rb13
-rw-r--r--spec/features/issues/note_polling_spec.rb14
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb4
-rw-r--r--spec/features/issues/spam_issues_spec.rb6
-rw-r--r--spec/features/issues/todo_spec.rb8
-rw-r--r--spec/features/issues/update_issues_spec.rb24
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb42
-rw-r--r--spec/features/issues_spec.rb78
-rw-r--r--spec/features/merge_requests/assign_issues_spec.rb4
-rw-r--r--spec/features/merge_requests/award_spec.rb10
-rw-r--r--spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb4
-rw-r--r--spec/features/merge_requests/cherry_pick_spec.rb6
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb4
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb10
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb26
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb5
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb8
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb12
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb8
-rw-r--r--spec/features/merge_requests/diffs_spec.rb16
-rw-r--r--spec/features/merge_requests/discussion_spec.rb10
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb6
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb4
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb16
-rw-r--r--spec/features/merge_requests/form_spec.rb16
-rw-r--r--spec/features/merge_requests/merge_commit_message_toggle_spec.rb4
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb4
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb6
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb6
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb4
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb6
-rw-r--r--spec/features/merge_requests/target_branch_spec.rb7
-rw-r--r--spec/features/merge_requests/toggle_whitespace_changes_spec.rb4
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb4
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb14
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb4
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb16
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb4
-rw-r--r--spec/features/merge_requests/user_sees_system_notes_spec.rb6
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb30
-rw-r--r--spec/features/merge_requests/versions_spec.rb7
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb4
-rw-r--r--spec/features/merge_requests/widget_spec.rb32
-rw-r--r--spec/features/merge_requests/wip_message_spec.rb14
-rw-r--r--spec/features/milestone_spec.rb31
-rw-r--r--spec/features/milestones/show_spec.rb4
-rw-r--r--spec/features/oauth_login_spec.rb112
-rw-r--r--spec/features/participants_autocomplete_spec.rb8
-rw-r--r--spec/features/profile_spec.rb2
-rw-r--r--spec/features/profiles/account_spec.rb2
-rw-r--r--spec/features/profiles/chat_names_spec.rb2
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb2
-rw-r--r--spec/features/profiles/password_spec.rb2
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/features/profiles/preferences_spec.rb2
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb2
-rw-r--r--spec/features/projects/activity/rss_spec.rb6
-rw-r--r--spec/features/projects/artifacts/browse_spec.rb2
-rw-r--r--spec/features/projects/artifacts/download_spec.rb8
-rw-r--r--spec/features/projects/artifacts/file_spec.rb2
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb2
-rw-r--r--spec/features/projects/badges/coverage_spec.rb7
-rw-r--r--spec/features/projects/badges/list_spec.rb4
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb18
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb28
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb6
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb8
-rw-r--r--spec/features/projects/branches/new_branch_ref_dropdown_spec.rb4
-rw-r--r--spec/features/projects/branches_spec.rb60
-rw-r--r--spec/features/projects/commit/builds_spec.rb4
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb4
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb6
-rw-r--r--spec/features/projects/commit/rss_spec.rb6
-rw-r--r--spec/features/projects/compare_spec.rb4
-rw-r--r--spec/features/projects/deploy_keys_spec.rb4
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb4
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb2
-rw-r--r--spec/features/projects/edit_spec.rb4
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb8
-rw-r--r--spec/features/projects/environments/environment_spec.rb16
-rw-r--r--spec/features/projects/environments/environments_spec.rb14
-rw-r--r--spec/features/projects/features_visibility_spec.rb44
-rw-r--r--spec/features/projects/files/browse_files_spec.rb4
-rw-r--r--spec/features/projects/files/creating_a_file_spec.rb9
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb9
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb4
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb4
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb4
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb4
-rw-r--r--spec/features/projects/files/find_files_spec.rb13
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb12
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb8
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb12
-rw-r--r--spec/features/projects/files/undo_template_spec.rb6
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb6
-rw-r--r--spec/features/projects/group_links_spec.rb8
-rw-r--r--spec/features/projects/guest_navigation_menu_spec.rb16
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb6
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb3
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb6
-rw-r--r--spec/features/projects/issuable_templates_spec.rb17
-rw-r--r--spec/features/projects/issues/list_spec.rb4
-rw-r--r--spec/features/projects/issues/rss_spec.rb7
-rw-r--r--spec/features/projects/jobs_spec.rb77
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb8
-rw-r--r--spec/features/projects/labels/subscription_spec.rb6
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb18
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb8
-rw-r--r--spec/features/projects/main/rss_spec.rb6
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb4
-rw-r--r--spec/features/projects/members/group_links_spec.rb6
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb4
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb4
-rw-r--r--spec/features/projects/members/group_members_spec.rb10
-rw-r--r--spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb4
-rw-r--r--spec/features/projects/members/list_spec.rb4
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb6
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb8
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb4
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb4
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb4
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb4
-rw-r--r--spec/features/projects/members/sorting_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb15
-rw-r--r--spec/features/projects/merge_request_button_spec.rb30
-rw-r--r--spec/features/projects/merge_requests/list_spec.rb12
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb8
-rw-r--r--spec/features/projects/milestones/milestones_sorting_spec.rb4
-rw-r--r--spec/features/projects/milestones/new_spec.rb2
-rw-r--r--spec/features/projects/new_project_spec.rb89
-rw-r--r--spec/features/projects/no_password_spec.rb8
-rw-r--r--spec/features/projects/pages_spec.rb8
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb103
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb10
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb18
-rw-r--r--spec/features/projects/project_settings_spec.rb18
-rw-r--r--spec/features/projects/ref_switcher_spec.rb4
-rw-r--r--spec/features/projects/services/jira_service_spec.rb10
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb8
-rw-r--r--spec/features/projects/services/slack_service_spec.rb4
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb8
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb12
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb4
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb6
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb14
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb8
-rw-r--r--spec/features/projects/shortcuts_spec.rb4
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb6
-rw-r--r--spec/features/projects/snippets/show_spec.rb8
-rw-r--r--spec/features/projects/snippets_spec.rb8
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb6
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb8
-rw-r--r--spec/features/projects/tree/rss_spec.rb6
-rw-r--r--spec/features/projects/user_create_dir_spec.rb6
-rw-r--r--spec/features/projects/user_creates_project_spec.rb2
-rw-r--r--spec/features/projects/view_on_env_spec.rb24
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb4
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb4
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb27
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb4
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb4
-rw-r--r--spec/features/projects/wiki/user_views_project_wiki_page_spec.rb11
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb10
-rw-r--r--spec/features/projects_spec.rb24
-rw-r--r--spec/features/protected_branches_spec.rb16
-rw-r--r--spec/features/protected_tags_spec.rb16
-rw-r--r--spec/features/reportable_note/commit_spec.rb6
-rw-r--r--spec/features/reportable_note/issue_spec.rb4
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb4
-rw-r--r--spec/features/reportable_note/snippets_spec.rb4
-rw-r--r--spec/features/runners_spec.rb6
-rw-r--r--spec/features/search_spec.rb18
-rw-r--r--spec/features/security/project/internal_access_spec.rb62
-rw-r--r--spec/features/security/project/private_access_spec.rb62
-rw-r--r--spec/features/security/project/public_access_spec.rb62
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb12
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb8
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb16
-rw-r--r--spec/features/snippets/explore_spec.rb4
-rw-r--r--spec/features/snippets/internal_snippet_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/features/snippets/search_snippets_spec.rb4
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb (renamed from spec/features/snippets/create_snippet_spec.rb)6
-rw-r--r--spec/features/snippets/user_deletes_snippet_spec.rb19
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb (renamed from spec/features/snippets/edit_snippet_spec.rb)26
-rw-r--r--spec/features/snippets/user_snippets_spec.rb2
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb10
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb8
-rw-r--r--spec/features/tags/master_updates_tag_spec.rb8
-rw-r--r--spec/features/tags/master_views_tags_spec.rb22
-rw-r--r--spec/features/task_lists_spec.rb16
-rw-r--r--spec/features/triggers_spec.rb20
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb4
-rw-r--r--spec/features/user_callout_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb29
-rw-r--r--spec/features/users/projects_spec.rb2
-rw-r--r--spec/features/users/rss_spec.rb3
-rw-r--r--spec/features/users/snippets_spec.rb2
-rw-r--r--spec/features/variables_spec.rb20
-rw-r--r--spec/finders/issues_finder_spec.rb140
-rw-r--r--spec/finders/labels_finder_spec.rb6
-rw-r--r--spec/finders/merge_requests_finder_spec.rb19
-rw-r--r--spec/finders/milestones_finder_spec.rb90
-rw-r--r--spec/finders/users_finder_spec.rb11
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/issues.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/merge_requests.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/issues.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json3
-rw-r--r--spec/fixtures/config/kubeconfig-without-ca.yml18
-rw-r--r--spec/fixtures/config/kubeconfig.yml19
-rw-r--r--spec/fixtures/markdown.md.erb22
-rw-r--r--spec/helpers/application_helper_spec.rb4
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb16
-rw-r--r--spec/helpers/groups_helper_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb57
-rw-r--r--spec/helpers/issues_helper_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb12
-rw-r--r--spec/helpers/milestones_helper_spec.rb36
-rw-r--r--spec/helpers/notes_helper_spec.rb14
-rw-r--r--spec/helpers/submodule_helper_spec.rb5
-rw-r--r--spec/initializers/8_metrics_spec.rb10
-rw-r--r--spec/javascripts/awards_handler_spec.js15
-rw-r--r--spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js2
-rw-r--r--spec/javascripts/close_reopen_report_toggle_spec.js270
-rw-r--r--spec/javascripts/emoji_spec.js (renamed from spec/javascripts/gl_emoji_spec.js)7
-rw-r--r--spec/javascripts/environments/environments_store_spec.js21
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js34
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb19
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb57
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml5
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js3
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js54
-rw-r--r--spec/javascripts/issue_spec.js65
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js1
-rw-r--r--spec/javascripts/lib/utils/dom_utils_spec.js35
-rw-r--r--spec/javascripts/merge_request_notes_spec.js2
-rw-r--r--spec/javascripts/merge_request_spec.js33
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js17
-rw-r--r--spec/javascripts/monitoring/deployments_spec.js133
-rw-r--r--spec/javascripts/monitoring/mock_data.js4230
-rw-r--r--spec/javascripts/monitoring/monitoring_column_spec.js109
-rw-r--r--spec/javascripts/monitoring/monitoring_deployment_spec.js137
-rw-r--r--spec/javascripts/monitoring/monitoring_flag_spec.js76
-rw-r--r--spec/javascripts/monitoring/monitoring_legends_spec.js111
-rw-r--r--spec/javascripts/monitoring/monitoring_row_spec.js57
-rw-r--r--spec/javascripts/monitoring/monitoring_spec.js49
-rw-r--r--spec/javascripts/monitoring/monitoring_state_spec.js110
-rw-r--r--spec/javascripts/monitoring/monitoring_store_spec.js24
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js98
-rw-r--r--spec/javascripts/monitoring/prometheus_mock_data.js1014
-rw-r--r--spec/javascripts/notes_spec.js45
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js26
-rw-r--r--spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js145
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js17
-rw-r--r--spec/javascripts/todos_spec.js4
-rw-r--r--spec/javascripts/visibility_select_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js66
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js302
-rw-r--r--spec/javascripts/zen_mode_spec.js3
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb14
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb25
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb62
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb22
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb14
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb29
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb10
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb34
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb6
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb8
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb17
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb74
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb78
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb76
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb22
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb33
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb4
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb3
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb38
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb217
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_spec.rb29
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb38
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml7
-rw-r--r--spec/lib/gitlab/import_export/project.json44
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml13
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb59
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb24
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb88
-rw-r--r--spec/lib/gitlab/metrics/influx_sampler_spec.rb (renamed from spec/lib/gitlab/metrics/sampler_spec.rb)10
-rw-r--r--spec/lib/gitlab/metrics/unicorn_sampler_spec.rb108
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb92
-rw-r--r--spec/lib/gitlab/popen_spec.rb13
-rw-r--r--spec/lib/gitlab/regex_spec.rb6
-rw-r--r--spec/lib/gitlab/shell_spec.rb87
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb53
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/mailers/notify_spec.rb43
-rw-r--r--spec/migrations/migrate_stage_id_reference_in_background_spec.rb68
-rw-r--r--spec/migrations/rename_duplicated_variable_key_spec.rb34
-rw-r--r--spec/models/ability_spec.rb11
-rw-r--r--spec/models/application_setting_spec.rb166
-rw-r--r--spec/models/blob_viewer/readme_spec.rb49
-rw-r--r--spec/models/ci/build_spec.rb108
-rw-r--r--spec/models/ci/group_variable_spec.rb31
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb17
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb7
-rw-r--r--spec/models/ci/pipeline_spec.rb39
-rw-r--r--spec/models/ci/variable_spec.rb45
-rw-r--r--spec/models/concerns/each_batch_spec.rb53
-rw-r--r--spec/models/concerns/feature_gate_spec.rb19
-rw-r--r--spec/models/concerns/has_variable_spec.rb43
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb13
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb46
-rw-r--r--spec/models/concerns/sortable_spec.rb21
-rw-r--r--spec/models/environment_spec.rb27
-rw-r--r--spec/models/forked_project_link_spec.rb76
-rw-r--r--spec/models/group_spec.rb66
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb15
-rw-r--r--spec/models/merge_request_diff_spec.rb6
-rw-r--r--spec/models/merge_request_spec.rb36
-rw-r--r--spec/models/milestone_spec.rb44
-rw-r--r--spec/models/namespace_spec.rb30
-rw-r--r--spec/models/project_group_link_spec.rb12
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb4
-rw-r--r--spec/models/project_services/jira_service_spec.rb8
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb48
-rw-r--r--spec/models/project_services/redmine_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb262
-rw-r--r--spec/models/project_wiki_spec.rb18
-rw-r--r--spec/models/repository_spec.rb26
-rw-r--r--spec/models/user_spec.rb57
-rw-r--r--spec/policies/base_policy_spec.rb6
-rw-r--r--spec/policies/ci/build_policy_spec.rb28
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb14
-rw-r--r--spec/policies/deploy_key_policy_spec.rb12
-rw-r--r--spec/policies/environment_policy_spec.rb12
-rw-r--r--spec/policies/global_policy_spec.rb34
-rw-r--r--spec/policies/group_policy_spec.rb116
-rw-r--r--spec/policies/issue_policy_spec.rb122
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb68
-rw-r--r--spec/policies/project_policy_spec.rb117
-rw-r--r--spec/policies/project_snippet_policy_spec.rb64
-rw-r--r--spec/policies/user_policy_spec.rb12
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb4
-rw-r--r--spec/presenters/ci/group_variable_presenter_spec.rb63
-rw-r--r--spec/presenters/ci/variable_presenter_spec.rb63
-rw-r--r--spec/requests/api/commit_statuses_spec.rb22
-rw-r--r--spec/requests/api/features_spec.rb192
-rw-r--r--spec/requests/api/helpers_spec.rb5
-rw-r--r--spec/requests/api/internal_spec.rb74
-rw-r--r--spec/requests/api/issues_spec.rb23
-rw-r--r--spec/requests/api/merge_requests_spec.rb25
-rw-r--r--spec/requests/api/namespaces_spec.rb35
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb2
-rw-r--r--spec/requests/api/project_snippets_spec.rb24
-rw-r--r--spec/requests/api/projects_spec.rb68
-rw-r--r--spec/requests/api/runner_spec.rb3
-rw-r--r--spec/requests/api/snippets_spec.rb21
-rw-r--r--spec/requests/api/users_spec.rb127
-rw-r--r--spec/requests/api/v3/projects_spec.rb3
-rw-r--r--spec/requests/api/v3/users_spec.rb23
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb22
-rw-r--r--spec/routing/project_routing_spec.rb73
-rw-r--r--spec/rubocop/cop/active_record_dependent_spec.rb33
-rw-r--r--spec/rubocop/cop/active_record_serialize_spec.rb (renamed from spec/rubocop/cop/activerecord_serialize_spec.rb)4
-rw-r--r--spec/rubocop/cop/in_batches_spec.rb19
-rw-r--r--spec/rubocop/cop/project_path_helper_spec.rb41
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb2
-rw-r--r--spec/services/access_token_validation_service_spec.rb43
-rw-r--r--spec/services/boards/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/delete_merged_branches_service_spec.rb8
-rw-r--r--spec/services/git_hooks_service_spec.rb7
-rw-r--r--spec/services/git_push_service_spec.rb12
-rw-r--r--spec/services/groups/destroy_service_spec.rb52
-rw-r--r--spec/services/issues/move_service_spec.rb65
-rw-r--r--spec/services/issues/update_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb69
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb54
-rw-r--r--spec/services/merge_requests/update_service_spec.rb6
-rw-r--r--spec/services/milestones/destroy_service_spec.rb51
-rw-r--r--spec/services/notification_recipient_service_spec.rb34
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb20
-rw-r--r--spec/services/system_note_service_spec.rb10
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb79
-rw-r--r--spec/support/api_helpers.rb18
-rw-r--r--spec/support/capybara_helpers.rb5
-rw-r--r--spec/support/cycle_analytics_helpers.rb4
-rw-r--r--spec/support/fake_migration_classes.rb8
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb22
-rw-r--r--spec/support/features/rss_shared_examples.rb4
-rwxr-xr-xspec/support/generate-seed-repo-rb2
-rw-r--r--spec/support/gitaly.rb10
-rw-r--r--spec/support/gitlab-git-test.git/HEAD1
-rw-r--r--spec/support/gitlab-git-test.git/README.md16
-rw-r--r--spec/support/gitlab-git-test.git/config7
-rw-r--r--spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idxbin0 -> 5496 bytes
-rw-r--r--spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.packbin0 -> 381502 bytes
-rw-r--r--spec/support/gitlab-git-test.git/packed-refs18
-rw-r--r--spec/support/gitlab-git-test.git/refs/heads/.gitkeep0
-rw-r--r--spec/support/gitlab-git-test.git/refs/tags/.gitkeep0
-rw-r--r--spec/support/issuable_shared_examples.rb31
-rw-r--r--spec/support/issue_helpers.rb2
-rw-r--r--spec/support/issue_tracker_service_shared_example.rb8
-rw-r--r--spec/support/login_helpers.rb26
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb108
-rw-r--r--spec/support/matchers/be_utf8.rb9
-rw-r--r--spec/support/merge_request_helpers.rb2
-rwxr-xr-xspec/support/prepare-gitlab-git-test-for-commit17
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb4
-rw-r--r--spec/support/routing_helpers.rb3
-rw-r--r--spec/support/seed_helper.rb2
-rw-r--r--spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb (renamed from spec/support/protected_branches/access_control_ce_shared_examples.rb)10
-rw-r--r--spec/support/sidekiq.rb6
-rw-r--r--spec/support/stub_env.rb36
-rw-r--r--spec/support/test_env.rb21
-rwxr-xr-xspec/support/unpack-gitlab-git-test38
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb3
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb7
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb (renamed from spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb)2
-rw-r--r--spec/workers/background_migration_worker_spec.rb33
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb14
-rw-r--r--spec/workers/post_receive_spec.rb38
-rw-r--r--yarn.lock15
1685 files changed, 31320 insertions, 12592 deletions
diff --git a/.eslintrc b/.eslintrc
index 73cd7ecf66d..c72a5e0335b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -11,6 +11,7 @@
"gon": false,
"localStorage": false
},
+ "parser": "babel-eslint",
"plugins": [
"filenames",
"import",
diff --git a/.gitignore b/.gitignore
index e529e33530a..0d6194dd1e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,3 +60,4 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
+/.rspec
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 08aef3dd8ee..f7ab0259448 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,7 +63,7 @@ stages:
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
only:
- /mysql/
- - /-stable$/
+ - /-stable/
- master@gitlab-org/gitlab-ce
- master@gitlab/gitlabhq
- tags@gitlab-org/gitlab-ce
@@ -362,6 +362,7 @@ db:migrate:reset-mysql:
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS
+ - cp config/gitlab.yml.example config/gitlab.yml
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS
@@ -474,8 +475,8 @@ codeclimate:
services:
- docker:dind
script:
- - docker pull codeclimate/codeclimate
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
+ - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts:
paths: [codeclimate.json]
@@ -550,3 +551,10 @@ cache gems:
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
+
+gitlab_git_test:
+ variables:
+ SETUP_DB: "false"
+ script:
+ - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
+ <<: *except-docs
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 35f4d7441e0..00000000000
--- a/.rspec
+++ /dev/null
@@ -1,2 +0,0 @@
---color
---format Fuubar
diff --git a/.rubocop.yml b/.rubocop.yml
index 32ec60f540b..9785e7626f9 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -965,6 +965,10 @@ RSpec/AnyInstance:
RSpec/BeEql:
Enabled: true
+# We don't enforce this as we use this technique in a few places.
+RSpec/BeforeAfterAll:
+ Enabled: false
+
# Check that the first argument to the top level describe is the tested class or
# module.
RSpec/DescribeClass:
@@ -1024,6 +1028,12 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: is_expected, should
+RSpec/ImplicitExpect:
+ Enabled: true
+ EnforcedStyle: is_expected
+
# Checks for the usage of instance variables.
RSpec/InstanceVariable:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 5ab4692dd60..2ec558e274f 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -6,10 +6,6 @@
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 54
-RSpec/BeforeAfterAll:
- Enabled: false
-
# Offense count: 233
RSpec/EmptyLineAfterFinalLet:
Enabled: false
@@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject:
RSpec/HookArgument:
Enabled: false
-# Offense count: 12
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: is_expected, should
-RSpec/ImplicitExpect:
- Enabled: false
-
# Offense count: 11
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: it_behaves_like, it_should_behave_like
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fe49ad4579..c15a59d25d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.3.5 (2017-07-05)
+
+- Remove "Remove from board" button from backlog and closed list. !12430
+- Do not delete protected branches when deleting all merged branches. !12624
+- Set default for Remove source branch to false.
+- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
+- Expires full_path cache after a repository is renamed/transferred.
+
+## 9.3.4 (2017-07-03)
+
+- Update gitlab-shell to 5.1.1 !12615
+
+## 9.3.3 (2017-06-30)
+
+- Fix head pipeline stored in merge request for external pipelines. !12478
+- Bring back branches badge to main project page. !12548
+- Fix diff of requirements.txt file by not matching newlines as part of package names.
+- Perform housekeeping only when an import of a fresh project is completed.
+- Fixed issue boards closed list not showing all closed issues.
+- Fixed multi-line markdown tooltip buttons in issue edit form.
+
+## 9.3.2 (2017-06-27)
+
+- API: Fix optional arugments for POST :id/variables. !12474
+- Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets.
+
## 9.3.1 (2017-06-26)
- Fix reversed breadcrumb order for nested groups. !12322
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 54d1a4f2a4a..04a373efe6b 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.13.0
+0.16.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 17b2ccd9bf9..8f0916f768f 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.4.3
+0.5.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index c20c645d7e4..ac14c3dfaa8 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.0.6
+5.1.1
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ccbccc3dc62..276cbf9e285 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-2.2.0
+2.3.0
diff --git a/Gemfile b/Gemfile
index 71d2eb1557c..233a50c6e47 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,6 @@ source 'https://rubygems.org'
gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
-gem 'bootsnap', '~> 1.1'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
@@ -255,7 +254,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.4.0'
+gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.7'
@@ -285,6 +284,7 @@ group :metrics do
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5'
+ gem 'raindrops', '~> 0.18'
end
group :development do
@@ -385,7 +385,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
-gem 'gitaly', '~> 0.9.0'
+gem 'gitaly', '~> 0.14.0'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index f4ddd30da1b..deaf3ded2f9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -83,8 +83,6 @@ GEM
bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- bootsnap (1.1.1)
- msgpack (~> 1.0)
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
@@ -278,7 +276,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly (0.9.0)
+ gitaly (0.14.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -465,7 +463,6 @@ GEM
minitest (5.7.0)
mmap2 (2.2.7)
mousetrap-rails (1.4.6)
- msgpack (1.1.0)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
@@ -599,8 +596,8 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
- prometheus-client-mmap (0.7.0.beta5)
- mmap2 (~> 2.2.6)
+ prometheus-client-mmap (0.7.0.beta8)
+ mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -658,7 +655,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
- raindrops (0.17.0)
+ raindrops (0.18.0)
rake (10.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
@@ -775,7 +772,7 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
- sentry-raven (2.4.0)
+ sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
@@ -930,7 +927,6 @@ DEPENDENCIES
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
- bootsnap (~> 1.1)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0)
@@ -980,7 +976,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly (~> 0.9.0)
+ gitaly (~> 0.14.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
@@ -1062,6 +1058,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
+ raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
@@ -1089,7 +1086,7 @@ DEPENDENCIES
scss_lint (~> 0.47.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
- sentry-raven (~> 2.4.0)
+ sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
diff --git a/VERSION b/VERSION
index d821c124047..be3d36737cc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.3.0-pre
+9.4.0-pre
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index ebe722061d7..18cd04b176a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -3,11 +3,6 @@
import Cookies from 'js-cookie';
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { glEmojiTag } from './behaviors/gl_emoji';
-import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
-
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
@@ -17,8 +12,6 @@ const requestAnimationFrame = window.requestAnimationFrame ||
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
-let categoryMap = null;
-
const categoryLabelMap = {
activity: 'Activity',
people: 'People',
@@ -30,49 +23,10 @@ const categoryLabelMap = {
flags: 'Flags',
};
-function buildCategoryMap() {
- return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => {
- const emojiInfo = emojiMap[emojiNameKey];
- if (currentCategoryMap[emojiInfo.category]) {
- currentCategoryMap[emojiInfo.category].push(emojiNameKey);
- }
-
- return currentCategoryMap;
- }, {
- activity: [],
- people: [],
- nature: [],
- food: [],
- travel: [],
- objects: [],
- symbols: [],
- flags: [],
- });
-}
-
-function renderCategory(name, emojiList, opts = {}) {
- return `
- <h5 class="emoji-menu-title">
- ${name}
- </h5>
- <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
- ${emojiList.map(emojiName => `
- <li class="emoji-menu-list-item">
- <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
- ${glEmojiTag(emojiName, {
- sprite: true,
- })}
- </button>
- </li>
- `).join('\n')}
- </ul>
- `;
-}
-
-export default class AwardsHandler {
- constructor() {
+class AwardsHandler {
+ constructor(emoji) {
+ this.emoji = emoji;
this.eventListeners = [];
- this.aliases = emojiAliases;
// If the user shows intent let's pre-build the menu
this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
const $menu = $('.emoji-menu');
@@ -81,8 +35,6 @@ export default class AwardsHandler {
this.createEmojiMenu();
});
}
- // Prebuild the categoryMap
- categoryMap = categoryMap || buildCategoryMap();
});
this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
e.stopPropagation();
@@ -107,10 +59,10 @@ export default class AwardsHandler {
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
- const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+ const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current');
- this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
+ this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
});
}
@@ -168,16 +120,16 @@ export default class AwardsHandler {
this.isCreatingEmojiMenu = true;
// Render the first category
- categoryMap = categoryMap || buildCategoryMap();
+ const categoryMap = this.emoji.getEmojiCategoryMap();
const categoryNameKey = Object.keys(categoryMap)[0];
const emojisInCategory = categoryMap[categoryNameKey];
- const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+ const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
// Render the frequently used
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
let frequentlyUsedCatgegory = '';
if (frequentlyUsedEmojis.length > 0) {
- frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
+ frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
menuListClass: 'frequent-emojis',
});
}
@@ -208,7 +160,7 @@ export default class AwardsHandler {
}
this.isAddingRemainingEmojiMenuCategories = true;
- categoryMap = categoryMap || buildCategoryMap();
+ const categoryMap = this.emoji.getEmojiCategoryMap();
// Avoid the jank and render the remaining categories separately
// This will take more time, but makes UI more responsive
@@ -220,7 +172,7 @@ export default class AwardsHandler {
promiseChain.then(() =>
new Promise((resolve) => {
const emojisInCategory = categoryMap[categoryNameKey];
- const categoryMarkup = renderCategory(
+ const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
@@ -245,6 +197,25 @@ export default class AwardsHandler {
});
}
+ renderCategory(name, emojiList, opts = {}) {
+ return `
+ <h5 class="emoji-menu-title">
+ ${name}
+ </h5>
+ <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
+ ${emojiList.map(emojiName => `
+ <li class="emoji-menu-list-item">
+ <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
+ ${this.emoji.glEmojiTag(emojiName, {
+ sprite: true,
+ })}
+ </button>
+ </li>
+ `).join('\n')}
+ </ul>
+ `;
+ }
+
positionMenu($menu, $addBtn) {
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
@@ -262,14 +233,8 @@ export default class AwardsHandler {
return $menu.css(css);
}
- addAward(
- votesBlock,
- awardUrl,
- emoji,
- checkMutuality,
- callback,
- ) {
- const normalizedEmoji = this.normalizeEmojiName(emoji);
+ addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
+ const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
@@ -279,16 +244,12 @@ export default class AwardsHandler {
$('.js-add-award.is-active').removeClass('is-active');
}
- addAwardToEmojiBar(
- votesBlock,
- emoji,
- checkForMutuality,
- ) {
+ addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) {
if (checkForMutuality || checkForMutuality === null) {
this.checkMutuality(votesBlock, emoji);
}
this.addEmojiToFrequentlyUsedList(emoji);
- const normalizedEmoji = this.normalizeEmojiName(emoji);
+ const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) {
@@ -413,7 +374,7 @@ export default class AwardsHandler {
createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = `
<button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
- ${glEmojiTag(emojiName)}
+ ${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
`;
@@ -478,12 +439,8 @@ export default class AwardsHandler {
return $('body, html').animate(options, 200);
}
- normalizeEmojiName(emoji) {
- return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji;
- }
-
addEmojiToFrequentlyUsedList(emoji) {
- if (isEmojiNameValid(emoji)) {
+ if (this.emoji.isEmojiNameValid(emoji)) {
this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
@@ -493,7 +450,7 @@ export default class AwardsHandler {
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
- inputName => isEmojiNameValid(inputName),
+ inputName => this.emoji.isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
@@ -535,21 +492,11 @@ export default class AwardsHandler {
}
}
- findMatchingEmojiElements(term) {
- const safeTerm = term.toLowerCase();
-
- const namesMatchingAlias = [];
- Object.keys(emojiAliases).forEach((alias) => {
- if (alias.indexOf(safeTerm) >= 0) {
- namesMatchingAlias.push(emojiAliases[alias]);
- }
- });
- const $matchingElements = namesMatchingAlias.concat(safeTerm)
- .reduce(
- ($result, searchTerm) =>
- $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)),
- $([]),
- );
+ findMatchingEmojiElements(query) {
+ const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
+ const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
+ const $matchingElements = $emojiElements
+ .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
return $matchingElements.closest('li').clone();
}
@@ -560,3 +507,12 @@ export default class AwardsHandler {
$('.emoji-menu').remove();
}
}
+
+let awardsHandlerPromise = null;
+export default function loadAwardsHandler(reload = false) {
+ if (!awardsHandlerPromise || reload) {
+ awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
+ .then(Emoji => new AwardsHandler(Emoji));
+ }
+ return awardsHandlerPromise;
+}
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index 3bea460dcc6..e00af4b2fa8 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,23 +1,8 @@
import autosize from 'vendor/autosize';
-$(() => {
- const $fields = $('.js-autosize');
+document.addEventListener('DOMContentLoaded', () => {
+ const autosizeEls = document.querySelectorAll('.js-autosize');
- $fields.on('autosize:resized', function resized() {
- const $field = $(this);
- $field.data('height', $field.outerHeight());
- });
-
- $fields.on('resize.autosize', function resize() {
- const $field = $(this);
- if ($field.data('height') !== $field.outerHeight()) {
- $field.data('height', $field.outerHeight());
- autosize.destroy($field);
- $field.css('max-height', window.outerHeight);
- }
- });
-
- autosize($fields);
- autosize.update($fields);
- $fields.css('resize', 'vertical');
+ autosize(autosizeEls);
+ autosize.update(autosizeEls);
});
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 36ce4fddb72..7e98e04303a 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,75 +1,9 @@
import installCustomElements from 'document-register-element';
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map';
-import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported';
+import isEmojiUnicodeSupported from '../emoji/support';
installCustomElements(window);
-const generatedUnicodeSupportMap = getUnicodeSupportMap();
-
-function emojiImageTag(name, src) {
- return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
-}
-
-function assembleFallbackImageSrc(inputName) {
- let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
- emojiAliases[inputName] : inputName;
- let emojiInfo = emojiMap[name];
- // Fallback to question mark for unknown emojis
- if (!emojiInfo) {
- name = 'grey_question';
- emojiInfo = emojiMap[name];
- }
- const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
-
- return fallbackImageSrc;
-}
-const glEmojiTagDefaults = {
- sprite: false,
- forceFallback: false,
-};
-function glEmojiTag(inputName, options) {
- const opts = Object.assign({}, glEmojiTagDefaults, options);
- let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
- emojiAliases[inputName] : inputName;
- let emojiInfo = emojiMap[name];
- // Fallback to question mark for unknown emojis
- if (!emojiInfo) {
- name = 'grey_question';
- emojiInfo = emojiMap[name];
- }
-
- const fallbackImageSrc = assembleFallbackImageSrc(name);
- const fallbackSpriteClass = `emoji-${name}`;
-
- const classList = [];
- if (opts.forceFallback && opts.sprite) {
- classList.push('emoji-icon');
- classList.push(fallbackSpriteClass);
- }
- const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
- const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
- let contents = emojiInfo.moji;
- if (opts.forceFallback && !opts.sprite) {
- contents = emojiImageTag(name, fallbackImageSrc);
- }
-
- return `
- <gl-emoji
- ${classAttribute}
- data-name="${name}"
- data-fallback-src="${fallbackImageSrc}"
- ${fallbackSpriteAttribute}
- data-unicode-version="${emojiInfo.unicodeVersion}"
- title="${emojiInfo.description}"
- >
- ${contents}
- </gl-emoji>
- `;
-}
-
-function installGlEmojiElement() {
+export default function installGlEmojiElement() {
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
@@ -90,18 +24,26 @@ function installGlEmojiElement() {
if (
emojiUnicode &&
isEmojiUnicode &&
- !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
+ !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
- } else if (hasImageFallback) {
- this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
- const src = assembleFallbackImageSrc(name);
- this.innerHTML = emojiImageTag(name, src);
+ import(/* webpackChunkName: 'emoji' */ '../emoji')
+ .then(({ emojiImageTag, emojiFallbackImageSrc }) => {
+ if (hasImageFallback) {
+ this.innerHTML = emojiImageTag(name, fallbackSrc);
+ } else {
+ const src = emojiFallbackImageSrc(name);
+ this.innerHTML = emojiImageTag(name, src);
+ }
+ })
+ .catch(() => {
+ // do nothing
+ });
}
}
};
@@ -110,9 +52,3 @@ function installGlEmojiElement() {
prototype: GlEmojiElementProto,
});
}
-
-export {
- installGlEmojiElement,
- glEmojiTag,
- emojiImageTag,
-};
diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js
deleted file mode 100644
index be4aeb32c46..00000000000
--- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-
-function isEmojiNameValid(inputName) {
- const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
- emojiAliases[inputName] : inputName;
-
- return name && emojiMap[name];
-}
-
-export default isEmojiNameValid;
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 5b931e6cfa6..44b2c974b9e 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -1,7 +1,7 @@
import './autosize';
import './bind_in_out';
import './details_behavior';
-import { installGlEmojiElement } from './gl_emoji';
+import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';
import './toggler_behavior';
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index c7afd4ead6b..590b7be36e3 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
},
milestoneTitle() {
return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
- }
+ },
+ canRemove() {
+ return !this.list.preset;
+ },
},
watch: {
detail: {
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 5597f128b80..6a900d4abd0 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
},
template: `
<div
- class="block list"
- v-if="list.type !== 'closed'">
+ class="block list">
<button
class="btn btn-default btn-block"
type="button"
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index c28f6e151a0..1dfa064acfd 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -13,25 +13,21 @@ window.Build = (function () {
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
- this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
- this.scrollOffsetPadding = 30;
this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
- this.scrollToBottom = this.scrollToBottom.bind(this);
- this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
- this.$scrollContainer = $('.js-scroll-container');
+ this.$topBar = $('.js-top-bar');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
@@ -63,13 +59,22 @@ window.Build = (function () {
.off('click')
.on('click', this.scrollToBottom.bind(this));
- const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
+ this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
- this.$scrollContainer
+ $(window)
.off('scroll')
.on('scroll', () => {
- this.hasBeenScrolled = true;
- scrollThrottled();
+ const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
+ if (contentHeight > this.windowSize) {
+ // means the user did not scroll, the content was updated.
+ this.windowSize = contentHeight;
+ } else {
+ // User scrolled
+ this.hasBeenScrolled = true;
+ this.toggleScrollAnimation(false);
+ }
+
+ this.scrollThrottled();
});
$(window)
@@ -77,60 +82,73 @@ window.Build = (function () {
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate();
+ this.initAffixTopArea();
- // eslint-disable-next-line
- this.getBuildTrace()
- .then(() => this.toggleScroll())
- .then(() => {
- if (!this.hasBeenScrolled) {
- this.scrollToBottom();
- }
- });
-
- this.verifyTopPosition();
+ this.getBuildTrace();
}
+ Build.prototype.initAffixTopArea = function () {
+ /**
+ If the browser does not support position sticky, it returns the position as static.
+ If the browser does support sticky, then we allow the browser to handle it, if not
+ then we default back to Bootstraps affix
+ **/
+ if (this.$topBar.css('position') !== 'static') return;
+
+ const offsetTop = this.$buildTrace.offset().top;
+
+ this.$topBar.affix({
+ offset: {
+ top: offsetTop,
+ },
+ });
+ };
+
Build.prototype.canScroll = function () {
- return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
+ return document.body.scrollHeight > window.innerHeight;
};
- /**
- * | | Up | Down |
- * |--------------------------|----------|----------|
- * | on scroll bottom | active | disabled |
- * | on scroll top | disabled | active |
- * | no scroll | disabled | disabled |
- * | on.('scroll') is on top | disabled | active |
- * | on('scroll) is on bottom | active | disabled |
- *
- */
Build.prototype.toggleScroll = function () {
- const currentPosition = this.$scrollContainer.scrollTop();
- const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
+ const currentPosition = document.body.scrollTop;
+ const windowHeight = window.innerHeight;
if (this.canScroll()) {
- if (currentPosition === 0) {
+ if (currentPosition > 0 &&
+ (document.body.scrollHeight - currentPosition !== windowHeight)) {
+ // User is in the middle of the log
+
+ this.toggleDisableButton(this.$scrollTopBtn, false);
+ this.toggleDisableButton(this.$scrollBottomBtn, false);
+ } else if (currentPosition === 0) {
+ // User is at Top of Build Log
+
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
- } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
+ } else if (document.body.scrollHeight - currentPosition === windowHeight) {
+ // User is at the bottom of the build log.
+
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
- } else {
- this.toggleDisableButton(this.$scrollTopBtn, false);
- this.toggleDisableButton(this.$scrollBottomBtn, false);
}
+ } else {
+ this.toggleDisableButton(this.$scrollTopBtn, true);
+ this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
- Build.prototype.scrollToTop = function () {
+ Build.prototype.scrollDown = function () {
+ document.body.scrollTop = document.body.scrollHeight;
+ };
+
+ Build.prototype.scrollToBottom = function () {
+ this.scrollDown();
this.hasBeenScrolled = true;
- this.$scrollContainer.scrollTop(0);
this.toggleScroll();
};
- Build.prototype.scrollToBottom = function () {
+ Build.prototype.scrollToTop = function () {
+ document.body.scrollTop = 0;
this.hasBeenScrolled = true;
- this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll();
};
@@ -143,47 +161,6 @@ window.Build = (function () {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
- /**
- * Build trace top position depends on the space ocupied by the elments rendered before
- */
- Build.prototype.verifyTopPosition = function () {
- const $buildPage = $('.build-page');
-
- const $flashError = $('.alert-wrapper');
- const $header = $('.build-header', $buildPage);
- const $runnersStuck = $('.js-build-stuck', $buildPage);
- const $startsEnvironment = $('.js-environment-container', $buildPage);
- const $erased = $('.js-build-erased', $buildPage);
- const prependTopDefault = 20;
-
- // header + navigation + margin
- let topPostion = 168;
-
- if ($header.length) {
- topPostion += $header.outerHeight();
- }
-
- if ($runnersStuck.length) {
- topPostion += $runnersStuck.outerHeight();
- }
-
- if ($startsEnvironment.length) {
- topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
- }
-
- if ($erased.length) {
- topPostion += $erased.outerHeight() + prependTopDefault;
- }
-
- if ($flashError.length) {
- topPostion += $flashError.outerHeight();
- }
-
- this.$buildTrace.css({
- top: topPostion,
- });
- };
-
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
@@ -196,10 +173,13 @@ window.Build = (function () {
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
+
if (log.state) {
this.state = log.state;
}
+ this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
+
if (log.append) {
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
@@ -220,16 +200,14 @@ window.Build = (function () {
}
if (!log.complete) {
- this.toggleScrollAnimation(true);
+ if (!this.hasBeenScrolled) {
+ this.toggleScrollAnimation(true);
+ } else {
+ this.toggleScrollAnimation(false);
+ }
Build.timeout = setTimeout(() => {
- //eslint-disable-next-line
- this.getBuildTrace()
- .then(() => {
- if (!this.hasBeenScrolled) {
- this.scrollToBottom();
- }
- });
+ this.getBuildTrace();
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
@@ -242,7 +220,13 @@ window.Build = (function () {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
- });
+ })
+ .then(() => {
+ if (!this.hasBeenScrolled) {
+ this.scrollDown();
+ }
+ })
+ .then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
@@ -254,14 +238,11 @@ window.Build = (function () {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
- this.$buildTrace
- .toggleClass('sidebar-expanded', shouldShow)
- .toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide);
- $('.js-build-page')
+ this.$topBar
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
@@ -274,17 +255,10 @@ window.Build = (function () {
Build.prototype.sidebarOnResize = function () {
this.toggleSidebar(this.shouldHideSidebarForViewport());
-
- this.verifyTopPosition();
-
- if (this.canScroll()) {
- this.toggleScroll();
- }
};
Build.prototype.sidebarOnClick = function () {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
- this.verifyTopPosition();
};
Build.prototype.updateArtifactRemoveDate = function () {
diff --git a/app/assets/javascripts/close_reopen_report_toggle.js b/app/assets/javascripts/close_reopen_report_toggle.js
new file mode 100644
index 00000000000..882d20671cc
--- /dev/null
+++ b/app/assets/javascripts/close_reopen_report_toggle.js
@@ -0,0 +1,97 @@
+import DropLab from './droplab/drop_lab';
+import ISetter from './droplab/plugins/input_setter';
+
+// Todo: Remove this when fixing issue in input_setter plugin
+const InputSetter = Object.assign({}, ISetter);
+
+class CloseReopenReportToggle {
+ constructor(opts = {}) {
+ this.dropdownTrigger = opts.dropdownTrigger;
+ this.dropdownList = opts.dropdownList;
+ this.button = opts.button;
+ }
+
+ initDroplab() {
+ this.reopenItem = this.dropdownList.querySelector('.reopen-item');
+ this.closeItem = this.dropdownList.querySelector('.close-item');
+
+ this.droplab = new DropLab();
+
+ const config = this.setConfig();
+
+ this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
+ }
+
+ updateButton(isClosed) {
+ this.toggleButtonType(isClosed);
+
+ this.button.blur();
+ }
+
+ toggleButtonType(isClosed) {
+ const [showItem, hideItem] = this.getButtonTypes(isClosed);
+
+ showItem.classList.remove('hidden');
+ showItem.classList.add('droplab-item-selected');
+
+ hideItem.classList.add('hidden');
+ hideItem.classList.remove('droplab-item-selected');
+
+ showItem.click();
+ }
+
+ getButtonTypes(isClosed) {
+ return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
+ }
+
+ setDisable(shouldDisable = true) {
+ if (shouldDisable) {
+ this.button.setAttribute('disabled', 'true');
+ this.dropdownTrigger.setAttribute('disabled', 'true');
+ } else {
+ this.button.removeAttribute('disabled');
+ this.dropdownTrigger.removeAttribute('disabled');
+ }
+ }
+
+ setConfig() {
+ const config = {
+ InputSetter: [
+ {
+ input: this.button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'data-value',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'title',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-button-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: this.dropdownTrigger,
+ valueAttribute: 'data-toggle-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-url',
+ inputAttribute: 'href',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-method',
+ inputAttribute: 'data-method',
+ },
+ ],
+ };
+
+ return config;
+ }
+}
+
+export default CloseReopenReportToggle;
diff --git a/app/assets/javascripts/comment_type_toggle.js b/app/assets/javascripts/comment_type_toggle.js
index df0ba86198c..c74184949df 100644
--- a/app/assets/javascripts/comment_type_toggle.js
+++ b/app/assets/javascripts/comment_type_toggle.js
@@ -1,5 +1,8 @@
import DropLab from './droplab/drop_lab';
-import InputSetter from './droplab/plugins/input_setter';
+import ISetter from './droplab/plugins/input_setter';
+
+// Todo: Remove this when fixing issue in input_setter plugin
+const InputSetter = Object.assign({}, ISetter);
class CommentTypeToggle {
constructor(opts = {}) {
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 725ec7b9c70..6a008112203 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,6 +1,8 @@
/* eslint-disable class-methods-use-this */
import './lib/utils/url_utility';
+import FilesCommentButton from './files_comment_button';
+import SingleFileDiff from './single_file_diff';
const UNFOLD_COUNT = 20;
let isBound = false;
@@ -8,8 +10,14 @@ let isBound = false;
class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
- $diffFile.singleFileDiff();
- $diffFile.filesCommentButton();
+
+ $diffFile.each((index, file) => {
+ if (!$.data(file, 'singleFileDiff')) {
+ $.data(file, 'singleFileDiff', new SingleFileDiff(file));
+ }
+ });
+
+ FilesCommentButton.init($diffFile);
$diffFile.each((index, file) => new gl.ImageFile(file));
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 517bdb6be09..c37249c060a 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container')
- .toggleClass('js-no-comment-btn', notesCount > 0)
+ .toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container')
- .toggleClass('js-no-comment-btn', notesCount > 0);
+ .toggleClass('no-comment-btn', notesCount > 0);
},
toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 31a86090242..ae19592ecbe 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,17 +1,13 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-/* global UsernameValidator */
-/* global ActiveTabMemoizer */
/* global ShortcutsNavigation */
/* global IssuableIndex */
/* global ShortcutsIssuable */
-/* global ZenMode */
/* global Milestone */
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
/* global Commit */
/* global NotificationsForm */
-/* global TreeView */
/* global NotificationsDropdown */
/* global GroupAvatar */
/* global LineHighlighter */
@@ -25,7 +21,6 @@
/* global ProjectAvatar */
/* global CompareAutocomplete */
/* global ProjectNew */
-/* global Star */
/* global ProjectShow */
/* global Labels */
/* global Shortcuts */
@@ -54,8 +49,19 @@ import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
+import SigninTabsMemoizer from './signin_tabs_memoizer';
+import Star from './star';
+import Todos from './todos';
+import TreeView from './tree';
+import UsagePing from './usage_ping';
+import UsernameValidator from './username_validator';
+import VersionCheckImage from './version_check_image';
+import Wikis from './wikis';
+import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
+import OAuthRememberMe from './oauth_remember_me';
+import PerformanceBar from './performance_bar';
(function() {
var Dispatcher;
@@ -126,7 +132,8 @@ import initExperimentalFlags from './experimental_flags';
break;
case 'sessions:new':
new UsernameValidator();
- new ActiveTabMemoizer();
+ new SigninTabsMemoizer();
+ new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
break;
case 'projects:boards:show':
case 'projects:boards:index':
@@ -161,7 +168,7 @@ import initExperimentalFlags from './experimental_flags';
new UsersSelect();
break;
case 'dashboard:todos:index':
- new gl.Todos();
+ new Todos();
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
@@ -209,8 +216,8 @@ import initExperimentalFlags from './experimental_flags';
new MilestoneSelect();
new gl.IssuableTemplateSelectors();
break;
- case 'projects:merge_requests:new':
- case 'projects:merge_requests:new_diffs':
+ case 'projects:merge_requests:creations:new':
+ case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
@@ -247,10 +254,6 @@ import initExperimentalFlags from './experimental_flags';
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
break;
- case "projects:merge_requests:diffs":
- new gl.Diff();
- new ZenMode();
- break;
case 'dashboard:activity':
new gl.Activities();
break;
@@ -319,7 +322,7 @@ import initExperimentalFlags from './experimental_flags';
new gl.Members();
new UsersSelect();
break;
- case 'projects:members:show':
+ case 'projects:project_members:index':
new gl.MemberExpirationDate('.js-access-expiration-date-groups');
new GroupsSelect();
new gl.MemberExpirationDate();
@@ -381,12 +384,12 @@ import initExperimentalFlags from './experimental_flags';
new BlobViewer();
break;
case 'help:index':
- gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
+ VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
break;
case 'search:show':
new Search();
break;
- case 'projects:repository:show':
+ case 'projects:settings:repository:show':
// Initialize Protected Branch Settings
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
@@ -396,7 +399,8 @@ import initExperimentalFlags from './experimental_flags';
// Initialize expandable settings panels
initSettingsPanels();
break;
- case 'projects:ci_cd:show':
+ case 'projects:settings:ci_cd:show':
+ case 'groups:settings:ci_cd:show':
new gl.ProjectVariables();
break;
case 'ci:lints:create':
@@ -433,7 +437,7 @@ import initExperimentalFlags from './experimental_flags';
new Admin();
switch (path[1]) {
case 'cohorts':
- new gl.UsagePing();
+ new UsagePing();
break;
case 'groups':
new UsersSelect();
@@ -485,7 +489,7 @@ import initExperimentalFlags from './experimental_flags';
new NotificationsDropdown();
break;
case 'wikis':
- new gl.Wikis();
+ new Wikis();
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
@@ -517,6 +521,10 @@ import initExperimentalFlags from './experimental_flags';
if (!shortcut_handler) {
new Shortcuts();
}
+
+ if (document.querySelector('#peek')) {
+ new PerformanceBar({ container: '#peek' });
+ }
};
Dispatcher.prototype.initSearch = function() {
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
new file mode 100644
index 00000000000..cac35d6eed5
--- /dev/null
+++ b/app/assets/javascripts/emoji/index.js
@@ -0,0 +1,99 @@
+import emojiMap from 'emojis/digests.json';
+import emojiAliases from 'emojis/aliases.json';
+
+export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+
+export function normalizeEmojiName(name) {
+ return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
+}
+
+export function isEmojiNameValid(name) {
+ return validEmojiNames.indexOf(name) >= 0;
+}
+
+export function filterEmojiNames(filter) {
+ const match = filter.toLowerCase();
+ return validEmojiNames.filter(name => name.indexOf(match) >= 0);
+}
+
+export function filterEmojiNamesByAlias(filter) {
+ return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name)));
+}
+
+let emojiCategoryMap;
+export function getEmojiCategoryMap() {
+ if (!emojiCategoryMap) {
+ emojiCategoryMap = {
+ activity: [],
+ people: [],
+ nature: [],
+ food: [],
+ travel: [],
+ objects: [],
+ symbols: [],
+ flags: [],
+ };
+ Object.keys(emojiMap).forEach((name) => {
+ const emoji = emojiMap[name];
+ if (emojiCategoryMap[emoji.category]) {
+ emojiCategoryMap[emoji.category].push(name);
+ }
+ });
+ }
+ return emojiCategoryMap;
+}
+
+export function getEmojiInfo(query) {
+ let name = normalizeEmojiName(query);
+ let emojiInfo = emojiMap[name];
+
+ // Fallback to question mark for unknown emojis
+ if (!emojiInfo) {
+ name = 'grey_question';
+ emojiInfo = emojiMap[name];
+ }
+
+ return { ...emojiInfo, name };
+}
+
+export function emojiFallbackImageSrc(inputName) {
+ const { name, digest } = getEmojiInfo(inputName);
+ return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`;
+}
+
+export function emojiImageTag(name, src) {
+ return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
+}
+
+export function glEmojiTag(inputName, options) {
+ const opts = { sprite: false, forceFallback: false, ...options };
+ const { name, ...emojiInfo } = getEmojiInfo(inputName);
+
+ const fallbackImageSrc = emojiFallbackImageSrc(name);
+ const fallbackSpriteClass = `emoji-${name}`;
+
+ const classList = [];
+ if (opts.forceFallback && opts.sprite) {
+ classList.push('emoji-icon');
+ classList.push(fallbackSpriteClass);
+ }
+ const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
+ const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
+ let contents = emojiInfo.moji;
+ if (opts.forceFallback && !opts.sprite) {
+ contents = emojiImageTag(name, fallbackImageSrc);
+ }
+
+ return `
+ <gl-emoji
+ ${classAttribute}
+ data-name="${name}"
+ data-fallback-src="${fallbackImageSrc}"
+ ${fallbackSpriteAttribute}
+ data-unicode-version="${emojiInfo.unicodeVersion}"
+ title="${emojiInfo.description}"
+ >
+ ${contents}
+ </gl-emoji>
+ `;
+}
diff --git a/app/assets/javascripts/emoji/support/index.js b/app/assets/javascripts/emoji/support/index.js
new file mode 100644
index 00000000000..1f7852dd487
--- /dev/null
+++ b/app/assets/javascripts/emoji/support/index.js
@@ -0,0 +1,10 @@
+import isEmojiUnicodeSupported from './is_emoji_unicode_supported';
+import getUnicodeSupportMap from './unicode_support_map';
+
+// cache browser support map between calls
+let browserUnicodeSupportMap;
+
+export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) {
+ browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap();
+ return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion);
+}
diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
index 4f8884d05ac..3fd23efa9f8 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js
+++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
@@ -111,7 +111,7 @@ function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVe
}
export {
- isEmojiUnicodeSupported,
+ isEmojiUnicodeSupported as default,
isFlagEmoji,
isKeycapEmoji,
isSkinToneComboEmoji,
diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js
index 257df55e54f..755381c2f95 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
+++ b/app/assets/javascripts/emoji/support/unicode_support_map.js
@@ -140,7 +140,7 @@ function generateUnicodeSupportMap(testMap) {
return resultMap;
}
-function getUnicodeSupportMap() {
+export default function getUnicodeSupportMap() {
let unicodeSupportMap;
let userAgentFromCache;
@@ -165,8 +165,3 @@ function getUnicodeSupportMap() {
return unicodeSupportMap;
}
-
-export {
- getUnicodeSupportMap,
- generateUnicodeSupportMap,
-};
diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue
index 8120ef182d4..91ed8c8467f 100644
--- a/app/assets/javascripts/environments/components/environment.vue
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -32,7 +32,6 @@ export default {
state: store.state,
visibility: 'available',
isLoading: false,
- isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
@@ -86,9 +85,6 @@ export default {
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
-
- // We need to verify if any folder is open to also fecth it
- this.openFolders = this.store.getOpenFolders();
},
});
@@ -119,7 +115,7 @@ export default {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
- this.fetchChildEnvironments(folder, folderUrl);
+ this.fetchChildEnvironments(folder, folderUrl, true);
}
},
@@ -147,19 +143,17 @@ export default {
.catch(this.errorCallback);
},
- fetchChildEnvironments(folder, folderUrl) {
- this.isLoadingFolderContent = true;
+ fetchChildEnvironments(folder, folderUrl, showLoader = false) {
+ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folderUrl)
.then(resp => resp.json())
- .then((response) => {
- this.store.setfolderContent(folder, response.environments);
- this.isLoadingFolderContent = false;
- })
+ .then(response => this.store.setfolderContent(folder, response.environments))
+ .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
- this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
+ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
@@ -176,13 +170,13 @@ export default {
successCallback(resp) {
this.saveData(resp);
- // If folders are open while polling we need to open them again
- if (this.openFolders.length) {
- this.openFolders.map((folder) => {
+ // We need to verify if any folder is open to also update it
+ const openFolders = this.store.getOpenFolders();
+ if (openFolders.length) {
+ openFolders.forEach((folder) => {
// TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
- this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl);
});
}
@@ -267,7 +261,7 @@ export default {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
- :is-loading-folder-content="isLoadingFolderContent" />
+ />
</div>
<table-pagination
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index b1fd9db650b..175cc8f1f72 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -29,12 +29,6 @@ export default {
required: false,
default: false,
},
-
- isLoadingFolderContent: {
- type: Boolean,
- required: false,
- default: false,
- },
},
methods: {
@@ -74,7 +68,7 @@ export default {
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
- <div v-if="isLoadingFolderContent">
+ <div v-if="model.isLoadingFolderContent">
<loading-icon size="2" />
</div>
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index a5773dd7e4f..038c149be2d 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -35,14 +35,18 @@ export default class EnvironmentsStore {
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
+ const oldEnvironmentState = this.state.environments
+ .find(element => element.id === env.latest.id) || {};
+
let filtered = {};
if (env.size > 1) {
filtered = Object.assign({}, env, {
isFolder: true,
+ isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name,
- isOpen: false,
- children: [],
+ isOpen: oldEnvironmentState.isOpen || false,
+ children: oldEnvironmentState.children || [],
});
}
@@ -98,7 +102,7 @@ export default class EnvironmentsStore {
* @return {Array}
*/
toggleFolder(folder) {
- return this.updateFolder(folder, 'isOpen', !folder.isOpen);
+ return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
}
/**
@@ -125,23 +129,23 @@ export default class EnvironmentsStore {
return updated;
});
- return this.updateFolder(folder, 'children', updatedEnvironments);
+ return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
}
/**
- * Given a folder a prop and a new value updates the correct folder.
+ * Given a environment, a prop and a new value updates the correct environment.
*
- * @param {Object} folder
+ * @param {Object} environment
* @param {String} prop
* @param {String|Boolean|Object|Array} newValue
* @return {Array}
*/
- updateFolder(folder, prop, newValue) {
+ updateEnvironmentProp(environment, prop, newValue) {
const environments = this.state.environments;
const updatedEnvironments = environments.map((env) => {
const updateEnv = Object.assign({}, env);
- if (env.isFolder && env.id === folder.id) {
+ if (env.id === environment.id) {
updateEnv[prop] = newValue;
}
@@ -149,8 +153,6 @@ export default class EnvironmentsStore {
});
this.state.environments = updatedEnvironments;
-
- return updatedEnvironments;
}
getOpenFolders() {
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 534e651b030..d02e4cd5876 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -1,150 +1,73 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
-/* global FilesCommentButton */
/* global notes */
-let $commentButtonTemplate;
-
-window.FilesCommentButton = (function() {
- var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
-
- COMMENT_BUTTON_CLASS = '.add-diff-note';
-
- LINE_HOLDER_CLASS = '.line_holder';
-
- LINE_NUMBER_CLASS = 'diff-line-num';
-
- LINE_CONTENT_CLASS = 'line_content';
-
- UNFOLDABLE_LINE_CLASS = 'js-unfold';
-
- EMPTY_CELL_CLASS = 'empty-cell';
-
- OLD_LINE_CLASS = 'old_line';
-
- LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
-
- TEXT_FILE_SELECTOR = '.text-file';
-
- function FilesCommentButton(filesContainerElement) {
- this.render = this.render.bind(this);
- this.hideButton = this.hideButton.bind(this);
- this.isParallelView = notes.isParallelView();
- filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
- .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
- }
-
- FilesCommentButton.prototype.render = function(e) {
- var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
- $currentTarget = $(e.currentTarget);
-
- if ($currentTarget.hasClass('js-no-comment-btn')) return;
-
- lineContentElement = this.getLineContent($currentTarget);
- buttonParentElement = this.getButtonParent($currentTarget);
-
- if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
-
- $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
- buttonParentElement.addClass('is-over')
- .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
-
- if ($button.length) {
- return;
+/* Developer beware! Do not add logic to showButton or hideButton
+ * that will force a reflow. Doing so will create a signficant performance
+ * bottleneck for pages with large diffs. For a comprehensive list of what
+ * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
+ */
+
+const LINE_NUMBER_CLASS = 'diff-line-num';
+const UNFOLDABLE_LINE_CLASS = 'js-unfold';
+const NO_COMMENT_CLASS = 'no-comment-btn';
+const EMPTY_CELL_CLASS = 'empty-cell';
+const OLD_LINE_CLASS = 'old_line';
+const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
+const DIFF_CONTAINER_SELECTOR = '.files';
+const DIFF_EXPANDED_CLASS = 'diff-expanded';
+
+export default {
+ init($diffFile) {
+ /* Caching is used only when the following members are *true*. This is because there are likely to be
+ * differently configured versions of diffs in the same session. However if these values are true, they
+ * will be true in all cases */
+
+ if (!this.userCanCreateNote) {
+ // data-can-create-note is an empty string when true, otherwise undefined
+ this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
}
- textFileElement = this.getTextFileElement($currentTarget);
- buttonParentElement.append(this.buildButton({
- discussionID: lineContentElement.attr('data-discussion-id'),
- lineType: lineContentElement.attr('data-line-type'),
-
- noteableType: textFileElement.attr('data-noteable-type'),
- noteableID: textFileElement.attr('data-noteable-id'),
- commitID: textFileElement.attr('data-commit-id'),
- noteType: lineContentElement.attr('data-note-type'),
-
- // LegacyDiffNote
- lineCode: lineContentElement.attr('data-line-code'),
-
- // DiffNote
- position: lineContentElement.attr('data-position')
- }));
- };
-
- FilesCommentButton.prototype.hideButton = function(e) {
- var $currentTarget = $(e.currentTarget);
- var buttonParentElement = this.getButtonParent($currentTarget);
-
- buttonParentElement.removeClass('is-over')
- .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
- };
-
- FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
- return $commentButtonTemplate.clone().attr({
- 'data-discussion-id': buttonAttributes.discussionID,
- 'data-line-type': buttonAttributes.lineType,
-
- 'data-noteable-type': buttonAttributes.noteableType,
- 'data-noteable-id': buttonAttributes.noteableID,
- 'data-commit-id': buttonAttributes.commitID,
- 'data-note-type': buttonAttributes.noteType,
-
- // LegacyDiffNote
- 'data-line-code': buttonAttributes.lineCode,
-
- // DiffNote
- 'data-position': buttonAttributes.position
- });
- };
-
- FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
- return hoveredElement.closest(TEXT_FILE_SELECTOR);
- };
-
- FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
- if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
- return hoveredElement;
- }
- if (!this.isParallelView) {
- return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
- } else {
- return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+ if (typeof notes !== 'undefined' && !this.isParallelView) {
+ this.isParallelView = notes.isParallelView && notes.isParallelView();
}
- };
- FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
- if (!this.isParallelView) {
- if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
- return hoveredElement;
- }
- return hoveredElement.parent().find("." + OLD_LINE_CLASS);
- } else {
- if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
- return hoveredElement;
- }
- return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+ if (this.userCanCreateNote) {
+ $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
+ .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
}
- };
+ },
- FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
- return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
- };
+ showButton(isParallelView, e) {
+ const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
- FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
- return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
- };
+ if (!this.validateButtonParent(buttonParentElement)) return;
- return FilesCommentButton;
-})();
+ buttonParentElement.classList.add('is-over');
+ buttonParentElement.nextElementSibling.classList.add('is-over');
+ },
-$.fn.filesCommentButton = function() {
- $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+ hideButton(isParallelView, e) {
+ const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
- if (!(this && (this.parent().data('can-create-note') != null))) {
- return;
- }
- return this.each(function() {
- if (!$.data(this, 'filesCommentButton')) {
- return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
+ buttonParentElement.classList.remove('is-over');
+ buttonParentElement.nextElementSibling.classList.remove('is-over');
+ },
+
+ getButtonParent(hoveredElement, isParallelView) {
+ if (isParallelView) {
+ if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) {
+ return hoveredElement.previousElementSibling;
+ }
+ } else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) {
+ return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`);
}
- });
+ return hoveredElement;
+ },
+
+ validateButtonParent(buttonParentElement) {
+ return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
+ !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
+ !buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
+ !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
+ },
};
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 65c1b2050ac..19fed771197 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -2,6 +2,7 @@
import AjaxFilter from '~/droplab/plugins/ajax_filter';
import './filtered_search_dropdown';
+import { addClassIfElementExists } from '../lib/utils/dom_utils';
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter) {
@@ -32,8 +33,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
}
hideCurrentUser() {
- const currenUserItem = this.dropdown.querySelector('.js-current-user');
- currenUserItem.classList.add('hidden');
+ addClassIfElementExists(this.dropdown.querySelector('.js-current-user'), 'hidden');
}
itemClicked(e) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 1425769d2de..7872e9e68ad 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,6 +3,7 @@ import RecentSearchesRoot from './recent_searches_root';
import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub';
+import { addClassIfElementExists } from '../lib/utils/dom_utils';
class FilteredSearchManager {
constructor(page) {
@@ -227,11 +228,7 @@ class FilteredSearchManager {
}
addInputContainerFocus() {
- const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
-
- if (inputContainer) {
- inputContainer.classList.add('focus');
- }
+ addClassIfElementExists(this.filteredSearchInput.closest('.filtered-search-box'), 'focus');
}
removeInputContainerFocus(e) {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 105762cb1ba..6cb9cfe1382 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,8 +1,5 @@
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { glEmojiTag } from '~/behaviors/gl_emoji';
-import glRegexp from '~/lib/utils/regexp';
-import AjaxCache from '~/lib/utils/ajax_cache';
+import glRegexp from './lib/utils/regexp';
+import AjaxCache from './lib/utils/ajax_cache';
function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
@@ -33,6 +30,7 @@ class GfmAutoComplete {
this.input.each((i, input) => {
const $input = $(input);
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
+ $input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
// This triggers at.js again
// Needed for quick actions with suffixes (ex: /label ~)
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
@@ -375,7 +373,12 @@ class GfmAutoComplete {
if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
- this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
+ import(/* webpackChunkName: 'emoji' */ './emoji')
+ .then(({ validEmojiNames, glEmojiTag }) => {
+ this.loadData($input, at, validEmojiNames);
+ GfmAutoComplete.glEmojiTag = glEmojiTag;
+ })
+ .catch(() => { this.isLoadingData[at] = false; });
} else {
AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true)
.then((data) => {
@@ -398,6 +401,13 @@ class GfmAutoComplete {
this.cachedData = {};
}
+ destroy() {
+ this.input.each((i, input) => {
+ const $input = $(input);
+ $input.atwho('destroy');
+ });
+ }
+
static isLoading(data) {
let dataToInspect = data;
if (data && data.length > 0) {
@@ -423,12 +433,14 @@ GfmAutoComplete.atTypeMap = {
};
// Emoji
+GfmAutoComplete.glEmojiTag = null;
GfmAutoComplete.Emoji = {
templateFunction(name) {
- return `<li>
- ${name} ${glEmojiTag(name)}
- </li>
- `;
+ // glEmojiTag helper is loaded on-demand in fetchData()
+ if (GfmAutoComplete.glEmojiTag) {
+ return `<li>${name} ${GfmAutoComplete.glEmojiTag(name)}</li>`;
+ }
+ return `<li>${name}</li>`;
},
};
// Team Members
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index dc9f114af99..4e8141b2956 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) {
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
+ if (this.autoComplete) {
+ this.autoComplete.destroy();
+ }
return this.form.data('gl-form', null);
};
@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
- new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
+ this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
+ this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
index 462d792b8d5..37c6765d942 100644
--- a/app/assets/javascripts/group_name.js
+++ b/app/assets/javascripts/group_name.js
@@ -1,13 +1,13 @@
-
+import Cookies from 'js-cookie';
import _ from 'underscore';
export default class GroupName {
constructor() {
- this.titleContainer = document.querySelector('.title-container');
- this.title = document.querySelector('.title');
+ this.titleContainer = document.querySelector('.js-title-container');
+ this.title = this.titleContainer.querySelector('.title');
this.titleWidth = this.title.offsetWidth;
- this.groupTitle = document.querySelector('.group-title');
- this.groups = document.querySelectorAll('.group-path');
+ this.groupTitle = this.titleContainer.querySelector('.group-title');
+ this.groups = this.titleContainer.querySelectorAll('.group-path');
this.toggle = null;
this.isHidden = false;
this.init();
@@ -33,11 +33,20 @@ export default class GroupName {
createToggle() {
this.toggle = document.createElement('button');
+ this.toggle.setAttribute('type', 'button');
this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path');
- this.toggle.innerHTML = '...';
+ if (Cookies.get('new_nav') === 'true') {
+ this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
+ } else {
+ this.toggle.innerHTML = '...';
+ }
this.toggle.addEventListener('click', this.toggleGroups.bind(this));
- this.titleContainer.insertBefore(this.toggle, this.title);
+ if (Cookies.get('new_nav') === 'true') {
+ this.title.insertBefore(this.toggle, this.groupTitle);
+ } else {
+ this.titleContainer.insertBefore(this.toggle, this.title);
+ }
this.toggleGroups();
}
diff --git a/app/assets/javascripts/helpers/issuables_helper.js b/app/assets/javascripts/helpers/issuables_helper.js
new file mode 100644
index 00000000000..52d0f7e43fc
--- /dev/null
+++ b/app/assets/javascripts/helpers/issuables_helper.js
@@ -0,0 +1,27 @@
+import CloseReopenReportToggle from '../close_reopen_report_toggle';
+
+function initCloseReopenReport() {
+ const container = document.querySelector('.js-issuable-close-dropdown');
+
+ if (!container) return undefined;
+
+ const dropdownTrigger = container.querySelector('.js-issuable-close-toggle');
+ const dropdownList = container.querySelector('.js-issuable-close-menu');
+ const button = container.querySelector('.js-issuable-close-button');
+
+ const closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ closeReopenReportToggle.initDroplab();
+
+ return closeReopenReportToggle;
+}
+
+const IssuablesHelper = {
+ initCloseReopenReport,
+};
+
+export default IssuablesHelper;
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index a8856120c5e..4f376599ba9 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -5,6 +5,7 @@
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import SidebarHeightManager from './sidebar_height_manager';
const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content';
@@ -56,18 +57,6 @@ export default class IssuableBulkUpdateSidebar {
return navbarHeight + layoutNavHeight + subNavScroll;
}
- initSidebar() {
- if (!this.navHeight) {
- this.navHeight = this.getNavHeight();
- }
-
- if (!this.sidebarInitialized) {
- $(document).off('scroll').on('scroll', _.throttle(this.setSidebarHeight, 10).bind(this));
- $(window).off('resize').on('resize', _.throttle(this.setSidebarHeight, 10).bind(this));
- this.sidebarInitialized = true;
- }
- }
-
setupBulkUpdateActions() {
IssuableBulkUpdateActions.setOriginalDropdownData();
}
@@ -97,7 +86,7 @@ export default class IssuableBulkUpdateSidebar {
this.toggleCheckboxDisplay(enable);
if (enable) {
- this.initSidebar();
+ SidebarHeightManager.init();
}
}
@@ -143,17 +132,6 @@ export default class IssuableBulkUpdateSidebar {
this.$bulkEditSubmitBtn.enable();
}
}
- // loosely based on method of the same name in right_sidebar.js
- setSidebarHeight() {
- const currentScrollDepth = window.pageYOffset || 0;
- const diff = this.navHeight - currentScrollDepth;
-
- if (diff > 0) {
- this.$sidebar.outerHeight(window.innerHeight - diff);
- } else {
- this.$sidebar.outerHeight('100%');
- }
- }
static getCheckedIssueIds() {
const $checkedIssues = $('.selected_issue:checked');
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 92f6f0d4117..9ac1325fc95 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,12 +1,12 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */
-/* global ZenMode */
/* global Autosave */
/* global dateFormat */
/* global Pikaday */
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
+import ZenMode from './zen_mode';
(function() {
this.IssuableForm = (function() {
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 0860e237ce1..2bee4fb045a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -4,13 +4,14 @@
import 'vendor/jquery.waitforimages';
import '~/lib/utils/text_utility';
import './flash';
-import './task_list';
+import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
+import IssuablesHelper from './helpers/issuables_helper';
class Issue {
constructor() {
if ($('a.btn-close').length) {
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
@@ -28,6 +29,11 @@ class Issue {
Issue.initMergeRequests();
Issue.initRelatedBranches();
+ this.closeButtons = $('a.btn-close');
+ this.reopenButtons = $('a.btn-reopen');
+
+ this.initCloseReopenReport();
+
if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
}
@@ -35,13 +41,8 @@ class Issue {
initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.';
- const closeButtons = $('a.btn-close');
- const isClosedBadge = $('div.status-box-closed');
- const isOpenBadge = $('div.status-box-open');
- const projectIssuesCounter = $('.issue_counter');
- const reopenButtons = $('a.btn-reopen');
- return closeButtons.add(reopenButtons).on('click', (e) => {
+ return $(document).on('click', 'a.btn-close, a.btn-reopen', (e) => {
var $button, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
@@ -50,7 +51,9 @@ class Issue {
if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form'));
}
- $button.prop('disabled', true);
+
+ this.disableCloseReopenButton($button);
+
url = $button.attr('href');
return $.ajax({
type: 'PUT',
@@ -58,15 +61,19 @@ class Issue {
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
+ const isClosedBadge = $('div.status-box-closed');
+ const isOpenBadge = $('div.status-box-open');
+ const projectIssuesCounter = $('.issue_counter');
+
if ('id' in data) {
$(document).trigger('issuable:change');
const isClosed = $button.hasClass('btn-close');
- closeButtons.toggleClass('hidden', isClosed);
- reopenButtons.toggleClass('hidden', !isClosed);
isClosedBadge.toggleClass('hidden', !isClosed);
isOpenBadge.toggleClass('hidden', isClosed);
+ this.toggleCloseReopenButton(isClosed);
+
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
@@ -83,12 +90,34 @@ class Issue {
} else {
new Flash(issueFailMessage);
}
-
- $button.prop('disabled', false);
+ })
+ .then(() => {
+ this.disableCloseReopenButton($button, false);
});
});
}
+ initCloseReopenReport() {
+ this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
+
+ if (this.closeButtons) this.closeButtons = this.closeButtons.not('.issuable-close-button');
+ if (this.reopenButtons) this.reopenButtons = this.reopenButtons.not('.issuable-close-button');
+ }
+
+ disableCloseReopenButton($button, shouldDisable) {
+ if (this.closeReopenReportToggle) {
+ this.closeReopenReportToggle.setDisable(shouldDisable);
+ } else {
+ $button.prop('disabled', shouldDisable);
+ }
+ }
+
+ toggleCloseReopenButton(isClosed) {
+ if (this.closeReopenReportToggle) this.closeReopenReportToggle.updateButton(isClosed);
+ this.closeButtons.toggleClass('hidden', isClosed);
+ this.reopenButtons.toggleClass('hidden', !isClosed);
+ }
+
static submitNoteForm(form) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 43db66c8e08..48bad8f1e68 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,5 +1,6 @@
<script>
import animateMixin from '../mixins/animate';
+ import TaskList from '../../task_list';
export default {
mixins: [animateMixin],
@@ -46,7 +47,7 @@
if (this.canUpdate) {
// eslint-disable-next-line no-new
- new gl.TaskList({
+ new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 4223a8fea49..d0145fed396 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -39,6 +39,17 @@
runnerId() {
return `#${this.job.runner.id}`;
},
+ renderBlock() {
+ return this.job.merge_request ||
+ this.job.duration ||
+ this.job.finished_data ||
+ this.job.erased_at ||
+ this.job.queued ||
+ this.job.runner ||
+ this.job.coverage ||
+ this.job.tags.length ||
+ this.job.cancel_path;
+ },
},
};
</script>
@@ -63,7 +74,7 @@
Retry
</a>
</div>
- <div class="block">
+ <div :class="{block : renderBlock }">
<p
class="build-detail-row js-job-mr"
v-if="job.merge_request">
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 939d17129de..f92e669414a 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
mounted() {
this.mediator.initBuildClass();
},
- updated() {
- // Wait for flash message to be appended
- Vue.nextTick(() => {
- if (this.mediator.build) {
- this.mediator.build.verifyTopPosition();
- }
- });
- },
render(createElement) {
return createElement('job-header', {
props: {
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index bfcc50996cc..1d1763c3963 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -112,29 +112,11 @@ window.dateFormat = dateFormat;
return timefor;
};
- w.gl.utils.cachedTimeagoElements = [];
w.gl.utils.renderTimeago = function($els) {
- if (!$els && !w.gl.utils.cachedTimeagoElements.length) {
- w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render'));
- } else if ($els) {
- w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray());
- }
-
- w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText);
- };
-
- w.gl.utils.updateTimeagoText = function(el) {
- const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), lang);
-
- if (el.textContent !== formattedDate) {
- el.textContent = formattedDate;
- }
- };
-
- w.gl.utils.initTimeagoTimeout = function() {
- gl.utils.renderTimeago();
+ const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
- gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000);
+ // timeago.js sets timeouts internally for each timeago value to be updated in real time
+ gl.utils.getTimeago().render(timeagoEls, lang);
};
w.gl.utils.getDayDifference = function(a, b) {
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
new file mode 100644
index 00000000000..de65ea15a60
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -0,0 +1,7 @@
+/* eslint-disable import/prefer-default-export */
+
+export const addClassIfElementExists = (element, className) => {
+ if (element) {
+ element.classList.add(className);
+ }
+};
diff --git a/app/assets/javascripts/locale/eo/app.js b/app/assets/javascripts/locale/eo/app.js
deleted file mode 100644
index 55f000e9b88..00000000000
--- a/app/assets/javascripts/locale/eo/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['eo'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-20 06:24-0400","Last-Translator":"Lyubomir Vasilev <lyubomirv@abv.bg>","Language-Team":"Esperanto (https://translate.zanata.org/project/view/GitLab)","Language":"eo","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"eo","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} enmetis %{commit_timeago}"],"About auto deploy":["Pri la aŭtomata disponigado"],"Active":["Aktiva"],"Activity":["Aktiveco"],"Add Changelog":["Aldoni liston de ŝanĝoj"],"Add Contribution guide":["Aldoni gvidliniojn por kontribuado"],"Add License":["Aldoni rajtigilon"],"Add an SSH key to your profile to pull or push via SSH.":["Aldonu SSH-ŝlosilon al via profilo por ebligi al vi eltiri kaj alpuŝi per SSH."],"Add new directory":["Aldoni novan dosierujon"],"Archived project! Repository is read-only":["Arkivita projekto! La deponejo permesas nur legadon"],"Are you sure you want to delete this pipeline schedule?":["Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"],"Attach a file by drag &amp; drop or %{upload_link}":["Alkroĉu dosieron per ŝovmetado aŭ %{upload_link}"],"Branch":["Branĉo","Branĉoj"],"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["La branĉo <strong>%{branch_name}</strong> estis kreita. Por agordi aŭtomatan disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn ŝanĝojn. %{link_to_autodeploy_doc}"],"Branches":["Branĉoj"],"Browse files":["Elekti dosierojn"],"ByAuthor|by":["de"],"CI configuration":["Agordoj de seninterrompa integrado"],"Cancel":["Nuligi"],"ChangeTypeActionLabel|Pick into branch":["Elekti en branĉon"],"ChangeTypeActionLabel|Revert in branch":["Malfari en branĉo"],"ChangeTypeAction|Cherry-pick":["Precize elekti"],"ChangeTypeAction|Revert":["Malfari"],"Changelog":["Listo de ŝanĝoj"],"Charts":["Diagramoj"],"Cherry-pick this commit":["Precize elekti ĉi tiun kunmetadon"],"Cherry-pick this merge request":["Precize elekti ĉi tiun peton pri kunfando"],"CiStatusLabel|canceled":["nuligita"],"CiStatusLabel|created":["kreita"],"CiStatusLabel|failed":["malsukcesa"],"CiStatusLabel|manual action":["mana ago"],"CiStatusLabel|passed":["sukcesa"],"CiStatusLabel|passed with warnings":["sukcesa, kun avertoj"],"CiStatusLabel|pending":["okazonta"],"CiStatusLabel|skipped":["transsaltita"],"CiStatusLabel|waiting for manual action":["atendanta manan agon"],"CiStatusText|blocked":["blokita"],"CiStatusText|canceled":["nuligita"],"CiStatusText|created":["kreita"],"CiStatusText|failed":["malsukcesa"],"CiStatusText|manual":["mana"],"CiStatusText|passed":["sukcesa"],"CiStatusText|pending":["okazonta"],"CiStatusText|skipped":["transsaltita"],"CiStatus|running":["plenumiĝanta"],"Commit":["Enmetado","Enmetadoj"],"Commit message":["Mesaĝo pri la enmetado"],"CommitBoxTitle|Commit":["Enmeti"],"CommitMessage|Add %{file_name}":["Aldoni „%{file_name}“"],"Commits":["Enmetadoj"],"Commits|History":["Historio"],"Committed by":["Enmetita de"],"Compare":["Kompari"],"Contribution guide":["Gvidlinioj por kontribuado"],"Contributors":["Kontribuantoj"],"Copy URL to clipboard":["Kopii la adreson en la kopibufron"],"Copy commit SHA to clipboard":["Kopii la identigilon de la enmetado"],"Create New Directory":["Krei novan dosierujon"],"Create directory":["Krei dosierujon"],"Create empty bare repository":["Krei malplenan deponejon"],"Create merge request":["Krei peton pri kunfando"],"Create new...":["Krei novan…"],"CreateNewFork|Fork":["Disbranĉigi"],"CreateTag|Tag":["Etikedo"],"Cron Timezone":["Horzono por Cron"],"Cron syntax":["La sintakso de Cron"],"Custom notification events":["Propraj sciigaj eventoj"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenado. Uzante la proprajn sciigajn nivelojn, vi ricevos ankaŭ sciigojn por elektitaj de vi eventoj. Por lerni pli, bonvolu vidi %{notification_link}."],"Cycle Analytics":["Cikla analizo"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["La cikla analizo esploras kiom da tempo necesas por disvolvi ideon ĝis ĝi fariĝos realaĵo."],"CycleAnalyticsStage|Code":["Programado"],"CycleAnalyticsStage|Issue":["Problemo"],"CycleAnalyticsStage|Plan":["Plano"],"CycleAnalyticsStage|Production":["Eldonado"],"CycleAnalyticsStage|Review":["Kontrolo"],"CycleAnalyticsStage|Staging":["Preparo por eldono"],"CycleAnalyticsStage|Test":["Testado"],"Define a custom pattern with cron syntax":["Difini propran ŝablonon, uzante la sintakson de Cron"],"Delete":["Forigi"],"Deploy":["Disponigado","Disponigadoj"],"Description":["Priskribo"],"Directory name":["Nomo de dosierujo"],"Don't show again":["Ne montru denove"],"Download":["Elŝuti"],"Download tar":["Elŝuti en formato „tar“"],"Download tar.bz2":["Elŝuti en formato „tar.bz2“"],"Download tar.gz":["Elŝuti en formato „tar.gz“"],"Download zip":["Elŝuti en formato „zip“"],"DownloadArtifacts|Download":["Elŝuti"],"DownloadCommit|Email Patches":["Sendi flikaĵojn per retpoŝto"],"DownloadCommit|Plain Diff":["Normala dosiero kun diferencoj"],"DownloadSource|Download":["Elŝuti"],"Edit":["Redakti"],"Edit Pipeline Schedule %{id}":["Redakti ĉenstablan planon %{id}"],"Every day (at 4:00am)":["Ĉiutage (je 4:00)"],"Every month (on the 1st at 4:00am)":["Ĉiumonate (en la 1a de la monato, je 4:00)"],"Every week (Sundays at 4:00am)":["Ĉiusemajne (en dimanĉo, je 4:00)"],"Failed to change the owner":["Ne eblas ŝanĝi la posedanton"],"Failed to remove the pipeline schedule":["Ne eblas forigi la ĉenstablan planon"],"Files":["Dosieroj"],"Find by path":["Trovi per dosierindiko"],"Find file":["Trovi dosieron"],"FirstPushedBy|First":["Unue"],"FirstPushedBy|pushed by":["alpuŝita de"],"Fork":["Disbranĉigo","Disbranĉigoj"],"ForkedFromProjectPath|Forked from":["Disbranĉigita el"],"From issue creation until deploy to production":["De la kreado de la problemo ĝis la disponigado en la publika versio"],"From merge request merge until deploy to production":["De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika versio"],"Go to your fork":["Al via disbranĉigo"],"GoToYourFork|Fork":["Disbranĉigo"],"Home":["Hejmo"],"Housekeeping successfully started":["La refreŝigo komenciĝis sukcese"],"Import repository":["Enporti deponejon"],"Interval Pattern":["Intervala ŝablono"],"Introducing Cycle Analytics":["Ni prezentas al vi la ciklan analizon"],"LFSStatus|Disabled":["Malŝaltita"],"LFSStatus|Enabled":["Ŝaltita"],"Last %d day":["La lasta %d tago","La lastaj %d tagoj"],"Last Pipeline":["Lasta ĉenstablo"],"Last Update":["Lasta ĝisdatigo"],"Last commit":["Lasta enmetado"],"Learn more in the":["Lernu pli en la"],"Learn more in the|pipeline schedules documentation":["dokumentado pri ĉenstablaj planoj"],"Leave group":["Forlasi la grupon"],"Leave project":["Forlasi la projekton"],"Limited to showing %d event at most":["Limigita al montrado de ne pli ol %d evento","Limigita al montrado de ne pli ol %d eventoj"],"Median":["Mediano"],"MissingSSHKeyWarningLink|add an SSH key":["aldonos SSH-ŝlosilon"],"New Issue":["Nova problemo","Novaj problemoj"],"New Pipeline Schedule":["Nova ĉenstabla plano"],"New branch":["Nova branĉo"],"New directory":["Nova dosierujo"],"New file":["Nova dosiero"],"New issue":["Nova problemo"],"New merge request":["Nova peto pri kunfando"],"New schedule":["Nova plano"],"New snippet":["Nova kodaĵo"],"New tag":["Nova etikedo"],"No repository":["Ne estas deponejo"],"No schedules":["Ne estas planoj"],"Not available":["Ne disponebla"],"Not enough data":["Ne estas sufiĉe da datenoj"],"Notification events":["Sciigaj eventoj"],"NotificationEvent|Close issue":["Fermi problemon"],"NotificationEvent|Close merge request":["Fermi peton pri kunfando"],"NotificationEvent|Failed pipeline":["Malsukcesa ĉenstablo"],"NotificationEvent|Merge merge request":["Apliki peton pri kunfando"],"NotificationEvent|New issue":["Nova problemo"],"NotificationEvent|New merge request":["Nova peto pri kunfando"],"NotificationEvent|New note":["Nova noto"],"NotificationEvent|Reassign issue":["Reatribui problemon"],"NotificationEvent|Reassign merge request":["Reatribui peton pri kunfando"],"NotificationEvent|Reopen issue":["Remalfermi problemon"],"NotificationEvent|Successful pipeline":["Sukcesa ĉenstablo"],"NotificationLevel|Custom":["Propraj"],"NotificationLevel|Disabled":["Malŝaltitaj"],"NotificationLevel|Global":["Ĝeneralaj"],"NotificationLevel|On mention":["Ĉe mencio"],"NotificationLevel|Participate":["Partoprenado"],"NotificationLevel|Watch":["Rigardado"],"OfSearchInADropdown|Filter":["Filtrilo"],"OpenedNDaysAgo|Opened":["Malfermita"],"Options":["Opcioj"],"Owner":["Posedanto"],"Pipeline":["Ĉenstablo"],"Pipeline Health":["Stato"],"Pipeline Schedule":["Ĉenstabla plano"],"Pipeline Schedules":["Ĉenstablaj planoj"],"PipelineSchedules|Activated":["Ŝaltita"],"PipelineSchedules|Active":["Ŝaltitaj"],"PipelineSchedules|All":["Ĉiuj"],"PipelineSchedules|Inactive":["Malŝaltitaj"],"PipelineSchedules|Next Run":["Sekvanta plenumo"],"PipelineSchedules|None":["Nenio"],"PipelineSchedules|Provide a short description for this pipeline":["Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"],"PipelineSchedules|Take ownership":["Akiri posedon"],"PipelineSchedules|Target":["Celo"],"PipelineSheduleIntervalPattern|Custom":["Propra"],"Pipeline|with stage":["kun etapo"],"Pipeline|with stages":["kun etapoj"],"Project '%{project_name}' queued for deletion.":["La projekto „%{project_name}“ estis alvicigita por forigado."],"Project '%{project_name}' was successfully created.":["La projekto „%{project_name}“ estis sukcese kreita."],"Project '%{project_name}' was successfully updated.":["La projekto „%{project_name}“ estis sukcese ĝisdatigita."],"Project '%{project_name}' will be deleted.":["La projekto „%{project_name}“ estos forigita."],"Project access must be granted explicitly to each user.":["Ĉiu uzanto devas akiri propran atingon al la projekto."],"Project export could not be deleted.":["Ne eblas forigi la projektan elporton."],"Project export has been deleted.":["La projekta elporto estis forigita."],"Project export link has expired. Please generate a new export from your project settings.":["La ligilo por la projekta elporto eksvalidiĝis. Bonvolu krei novan elporton en la agordoj de la projekto."],"Project export started. A download link will be sent by email.":["La elporto de la projekto komenciĝis. Vi ricevos ligilon per retpoŝto por elŝuti la datenoj."],"Project home":["Hejmo de la projekto"],"ProjectFeature|Disabled":["Malŝaltita"],"ProjectFeature|Everyone with access":["Ĉiu, kiu havas atingon"],"ProjectFeature|Only team members":["Nur skipanoj"],"ProjectFileTree|Name":["Nomo"],"ProjectLastActivity|Never":["Neniam"],"ProjectLifecycle|Stage":["Etapo"],"ProjectNetworkGraph|Graph":["Grafeo"],"Read more":["Legu pli"],"Readme":["LeguMin"],"RefSwitcher|Branches":["Branĉoj"],"RefSwitcher|Tags":["Etikedoj"],"Related Commits":["Rilataj enmetadoj"],"Related Deployed Jobs":["Rilataj disponigitaj taskoj"],"Related Issues":["Rilataj problemoj"],"Related Jobs":["Rilataj taskoj"],"Related Merge Requests":["Rilataj petoj pri kunfando"],"Related Merged Requests":["Rilataj aplikitaj petoj pri kunfando"],"Remind later":["Rememorigu denove"],"Remove project":["Forigi la projekton"],"Request Access":["Peti atingeblon"],"Revert this commit":["Malfari ĉi tiun enmetadon"],"Revert this merge request":["Malfari ĉi tiun peton pri kunfando"],"Save pipeline schedule":["Konservi ĉenstablan planon"],"Schedule a new pipeline":["Plani novan ĉenstablon"],"Scheduling Pipelines":["Planado de la ĉenstabloj"],"Search branches and tags":["Serĉu branĉon aŭ etikedon"],"Select Archive Format":["Elektu formaton de arkivo"],"Select a timezone":["Elektu horzonon"],"Select target branch":["Elektu celan branĉon"],"Set a password on your account to pull or push via %{protocol}":["Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per %{protocol}"],"Set up CI":["Agordi SI"],"Set up Koding":["Agordi „Koding“"],"Set up auto deploy":["Agordi aŭtomatan disponigadon"],"SetPasswordToCloneLink|set a password":["kreos pasvorton"],"Showing %d event":["Estas montrata %d evento","Estas montrataj %d eventoj"],"Source code":["Kodo"],"StarProject|Star":["Steligi"],"Start a %{new_merge_request} with these changes":["Kreu %{new_merge_request} kun ĉi tiuj ŝanĝoj"],"Switch branch/tag":["Iri al branĉo/etikedo"],"Tag":["Etikedo","Etikedoj"],"Tags":["Etikedoj"],"Target Branch":["Cela branĉo"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapo de programado montras la tempon de la unua enmetado ĝis la kreado de la peto pri kunfando. La datenoj aldoniĝos aŭtomate ĉi tie post kiam vi kreas la unuan peton pri kunfando."],"The collection of events added to the data gathered for that stage.":["La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la etapo."],"The fork relationship has been removed.":["La rilato de disbranĉigo estis forigita."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo ĝis la atribuado de la problemo al cela etapo de la projekto, aŭ al listo sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi tiu etapo."],"The phase of the development lifecycle.":["La etapo de la disvolva ciklo."],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por difinitaj branĉoj aŭ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan atingon al la projekto de la rilata uzanto."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapo de la plano montras la tempon de la antaŭa ŝtupo ĝis la alpuŝado de via unua enmetado. Ĉi tiu tempo aldoniĝos aŭtomate post kiam vi alpuŝas la unuan enmetadon."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapo de eldonado montras la tutan tempon de la kreado de problemo ĝis la disponigado en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi kompletigos plenan ciklon de ideo ĝis realaĵo."],"The project can be accessed by any logged in user.":["Ĉiu ensalutita uzanto havas atingon al la projekto"],"The project can be accessed without any authentication.":["Ĉiu povas havi atingon al la projekto, sen ensaluti"],"The repository for this project does not exist.":["La deponejo por ĉi tiu projekto ne ekzistas."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapo de la kontrolo montras la tempon de la kreado de la peto pri kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi aplikos la unuan peton pri kunfando."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapo de preparo por eldono montras la tempon inter la aplikado de la peto pri kunfando kaj la disponigado de la kodo en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la publika versio."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapo de testado montras kiom da tempo necesas al „GitLab CI“ por plenumi ĉiujn ĉenstablojn por la rilata peto pri kunfando. La datenoj aldoniĝos aŭtomate post kiam via unua ĉenstablo finiĝos."],"The time taken by each data entry gathered by that stage.":["La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valoro, kiu troviĝas en la mezo de aro da rigardataj valoroj. Ekzemple: inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malplenan deponejon aŭ enportos jam ekzistantan."],"Time before an issue gets scheduled":["Tempo antaŭ problemo estas planita por ellabori"],"Time before an issue starts implementation":["Tempo antaŭ la komenco de laboro super problemo"],"Time between merge request creation and merge/close":["Tempo inter la kreado de poeto pri kunfando kaj ĝia aplikado/fermado"],"Time until first merge request":["Tempo ĝis la unua peto pri kunfando"],"Timeago|%s days ago":["antaŭ %s tagoj"],"Timeago|%s days remaining":["restas %s tagoj"],"Timeago|%s hours remaining":["restas %s horoj"],"Timeago|%s minutes ago":["antaŭ %s minutoj"],"Timeago|%s minutes remaining":["restas %s minutoj"],"Timeago|%s months ago":["antaŭ %s monatoj"],"Timeago|%s months remaining":["restas %s monatoj"],"Timeago|%s seconds remaining":["restas %s sekundoj"],"Timeago|%s weeks ago":["antaŭ %s semajnoj"],"Timeago|%s weeks remaining":["restas %s semajnoj"],"Timeago|%s years ago":["antaŭ %s jaroj"],"Timeago|%s years remaining":["restas %s jaroj"],"Timeago|1 day remaining":["restas 1 tago"],"Timeago|1 hour remaining":["restas 1 horo"],"Timeago|1 minute remaining":["restas 1 minuto"],"Timeago|1 month remaining":["restas 1 monato"],"Timeago|1 week remaining":["restas 1 semajno"],"Timeago|1 year remaining":["restas 1 jaro"],"Timeago|Past due":["Malfruiĝis"],"Timeago|a day ago":["antaŭ unu tago"],"Timeago|a month ago":["antaŭ unu monato"],"Timeago|a week ago":["antaŭ unu semajno"],"Timeago|a while":["antaŭ iom da tempo"],"Timeago|a year ago":["antaŭ unu jaro"],"Timeago|about %s hours ago":["antaŭ ĉirkaŭ %s horoj"],"Timeago|about a minute ago":["antaŭ ĉirkaŭ unu minuto"],"Timeago|about an hour ago":["antaŭ ĉirkaŭ unu horo"],"Timeago|in %s days":["post %s tagoj"],"Timeago|in %s hours":["post %s horoj"],"Timeago|in %s minutes":["post %s minutoj"],"Timeago|in %s months":["post %s monatoj"],"Timeago|in %s seconds":["post %s sekundoj"],"Timeago|in %s weeks":["post %s semajnoj"],"Timeago|in %s years":["post %s jaroj"],"Timeago|in 1 day":["post 1 tago"],"Timeago|in 1 hour":["post 1 horo"],"Timeago|in 1 minute":["post 1 minuto"],"Timeago|in 1 month":["post 1 monato"],"Timeago|in 1 week":["post 1 semajno"],"Timeago|in 1 year":["post 1 jaro"],"Timeago|less than a minute ago":["antaŭ malpli ol minuto"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Totala tempo"],"Total test time for all commits/merges":["Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"],"Unstar":["Malsteligi"],"Upload New File":["Alŝuti novan dosieron"],"Upload file":["Alŝuti dosieron"],"Use your global notification setting":["Uzi vian ĝeneralan agordon pri la sciigoj"],"VisibilityLevel|Internal":["Interna"],"VisibilityLevel|Private":["Privata"],"VisibilityLevel|Public":["Publika"],"Want to see the data? Please ask an administrator for access.":["Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."],"We don't have enough data to show this stage.":["Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."],"Withdraw Access Request":["Nuligi la peton pri atingeblo"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Vi forigos „%{project_name_with_namespace}“.\\nOni NE POVAS malfari la forigon de projekto!\\nĈu vi estas ABSOLUTE certa?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vi transigos „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas ABSOLUTE certa?"],"You can only add files when you are on a branch":["Oni povas aldoni dosierojn nur kiam oni estas en branĉo"],"You have reached your project limit":["Vi ne povas krei pliajn projektojn"],"You must sign in to star a project":["Oni devas ensaluti por steligi projekton"],"You need permission.":["VI bezonas permeson."],"You will not get any notifications via email":["VI ne ricevos sciigojn per retpoŝto"],"You will only receive notifications for the events you choose":["Vi ricevos sciigojn nur por la eventoj elektitaj de vi"],"You will only receive notifications for threads you have participated in":["Vi ricevos sciigojn nur por la fadenoj, en kiuj vi partoprenis"],"You will receive notifications for any activity":["Vi ricevos sciigojn por ĉiu ago"],"You will receive notifications only for comments in which you were @mentioned":["Vi ricevos sciigojn nur por komentoj, en kiuj vi estas @menciita"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Vi ne povos eltiri aŭ alpuŝi kodon per %{protocol} antaŭ ol vi %{set_password_link} por via konto"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Vi ne povos eltiri aŭ alpuŝi kodon per SSH antaŭ ol vi %{add_ssh_key_link} al via profilo"],"Your name":["Via nomo"],"day":["tago","tagoj"],"new merge request":["novan peton pri kunfando"],"notification emails":["sciigoj per retpoŝto"],"parent":["patro","patroj"]}}}; \ No newline at end of file
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index d27b4ec78c6..892b3fab1c6 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -70,7 +70,7 @@ import './ajax_loading_spinner';
import './api';
import './aside';
import './autosave';
-import AwardsHandler from './awards_handler';
+import loadAwardsHandler from './awards_handler';
import './breakpoints';
import './broadcast_message';
import './build';
@@ -143,26 +143,13 @@ import './render_math';
import './right_sidebar';
import './search';
import './search_autocomplete';
-import './signin_tabs_memoizer';
-import './single_file_diff';
import './smart_interval';
import './snippets_list';
import './star';
import './subscription';
import './subscription_select';
import './syntax_highlight';
-import './task_list';
-import './todos';
-import './tree';
-import './usage_ping';
import './user';
-import './user_tabs';
-import './username_validator';
-import './users_select';
-import './version_check_image';
-import './visibility_select';
-import './wikis';
-import './zen_mode';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
@@ -355,10 +342,10 @@ $(function () {
$window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize();
});
- gl.awardsHandler = new AwardsHandler();
+ loadAwardsHandler();
new Aside();
- gl.utils.initTimeagoTimeout();
+ gl.utils.renderTimeago();
$(document).trigger('init.scrolling-tabs');
});
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index f93feeec1c2..0db2abe507d 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,8 +2,9 @@
/* global MergeRequestTabs */
import 'vendor/jquery.waitforimages';
-import './task_list';
+import TaskList from './task_list';
import './merge_request_tabs';
+import IssuablesHelper from './helpers/issuables_helper';
(function() {
this.MergeRequest = (function() {
@@ -21,11 +22,14 @@ import './merge_request_tabs';
return _this.showAllCommits();
};
})(this));
+
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
+ this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
+
if ($("a.btn-close").length) {
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
@@ -64,11 +68,15 @@ import './merge_request_tabs';
if (shouldSubmit && $this.data('submitted')) {
return;
}
+
+ if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
+
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
- return _this.submitNoteForm($this.closest('form'), $this);
+
+ _this.submitNoteForm($this.closest('form'), $this);
}
}
});
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 786b6014dc6..7840f05a8ae 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.resetViewContainer();
this.mountPipelinesView();
} else {
- this.expandView();
+ if (Breakpoints.get().getBreakpointSize() !== 'xs') {
+ this.expandView();
+ }
this.resetViewContainer();
this.destroyPipelinesView();
}
@@ -168,9 +170,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Activate a tab based on the current action
activateTab(action) {
- const activate = action === 'show' ? 'notes' : action;
// important note: the .tab('show') method triggers 'shown.bs.tab' event itself
- $(`.merge-request-tabs a[data-action='${activate}']`).tab('show');
+ $(`.merge-request-tabs a[data-action='${action}']`).tab('show');
}
// Replaces the current Merge Request-specific action in the URL with a new one
@@ -185,7 +186,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
//
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
- // setCurrentAction('notes')
+ // setCurrentAction('show')
// location.pathname # => "/namespace/project/merge_requests/1"
//
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
@@ -194,13 +195,13 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
//
// Returns the new URL String
setCurrentAction(action) {
- this.currentAction = action === 'show' ? 'notes' : action;
+ this.currentAction = action;
- // Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs'
- let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
+ // Remove a trailing '/commits' '/diffs' '/pipelines'
+ let newState = location.pathname.replace(/\/(commits|diffs|pipelines)(\.html)?\/?$/, '');
// Append the new action if we're on a tab other than 'notes'
- if (this.currentAction !== 'notes') {
+ if (this.currentAction !== 'show' && this.currentAction !== 'new') {
newState += `/${this.currentAction}`;
}
diff --git a/app/assets/javascripts/monitoring/components/monitoring.vue b/app/assets/javascripts/monitoring/components/monitoring.vue
new file mode 100644
index 00000000000..a6a2d3119e3
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring.vue
@@ -0,0 +1,157 @@
+<script>
+ /* global Flash */
+ import _ from 'underscore';
+ import statusCodes from '../../lib/utils/http_status';
+ import MonitoringService from '../services/monitoring_service';
+ import monitoringRow from './monitoring_row.vue';
+ import monitoringState from './monitoring_state.vue';
+ import MonitoringStore from '../stores/monitoring_store';
+ import eventHub from '../event_hub';
+
+ export default {
+
+ data() {
+ const metricsData = document.querySelector('#prometheus-graphs').dataset;
+ const store = new MonitoringStore();
+
+ return {
+ store,
+ state: 'gettingStarted',
+ hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics),
+ documentationPath: metricsData.documentationPath,
+ settingsPath: metricsData.settingsPath,
+ endpoint: metricsData.additionalMetrics,
+ deploymentEndpoint: metricsData.deploymentEndpoint,
+ showEmptyState: true,
+ backOffRequestCounter: 0,
+ updateAspectRatio: false,
+ updatedAspectRatios: 0,
+ resizeThrottled: {},
+ };
+ },
+
+ components: {
+ monitoringRow,
+ monitoringState,
+ },
+
+ methods: {
+ getGraphsData() {
+ const maxNumberOfRequests = 3;
+ this.state = 'loading';
+ gl.utils.backOff((next, stop) => {
+ this.service.get().then((resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ this.backOffRequestCounter = this.backOffRequestCounter += 1;
+ if (this.backOffRequestCounter < maxNumberOfRequests) {
+ next();
+ } else {
+ stop(new Error('Failed to connect to the prometheus server'));
+ }
+ } else {
+ stop(resp);
+ }
+ }).catch(stop);
+ })
+ .then((resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ this.state = 'unableToConnect';
+ return false;
+ }
+ return resp.json();
+ })
+ .then((metricGroupsData) => {
+ if (!metricGroupsData) return false;
+ this.store.storeMetrics(metricGroupsData.data);
+ return this.getDeploymentData();
+ })
+ .then((deploymentData) => {
+ if (deploymentData !== false) {
+ this.store.storeDeploymentData(deploymentData.deployments);
+ this.showEmptyState = false;
+ }
+ return {};
+ })
+ .catch(() => {
+ this.state = 'unableToConnect';
+ });
+ },
+
+ getDeploymentData() {
+ return this.service.getDeploymentData(this.deploymentEndpoint)
+ .then(resp => resp.json())
+ .catch(() => new Flash('Error getting deployment information.'));
+ },
+
+ resize() {
+ this.updateAspectRatio = true;
+ },
+
+ toggleAspectRatio() {
+ this.updatedAspectRatios = this.updatedAspectRatios += 1;
+ if (this.store.getMetricsCount() === this.updatedAspectRatios) {
+ this.updateAspectRatio = !this.updateAspectRatio;
+ this.updatedAspectRatios = 0;
+ }
+ },
+
+ },
+
+ created() {
+ this.service = new MonitoringService(this.endpoint);
+ eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+ },
+
+ beforeDestroy() {
+ eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
+ window.removeEventListener('resize', this.resizeThrottled, false);
+ },
+
+ mounted() {
+ this.resizeThrottled = _.throttle(this.resize, 600);
+ if (!this.hasMetrics) {
+ this.state = 'gettingStarted';
+ } else {
+ this.getGraphsData();
+ window.addEventListener('resize', this.resizeThrottled, false);
+ }
+ },
+ };
+</script>
+<template>
+ <div
+ class="prometheus-graphs"
+ v-if="!showEmptyState">
+ <div
+ class="row"
+ v-for="(groupData, index) in store.groups"
+ :key="index">
+ <div
+ class="col-md-12">
+ <div
+ class="panel panel-default prometheus-panel">
+ <div
+ class="panel-heading">
+ <h4>{{groupData.group}}</h4>
+ </div>
+ <div
+ class="panel-body">
+ <monitoring-row
+ v-for="(row, index) in groupData.metrics"
+ :key="index"
+ :row-data="row"
+ :update-aspect-ratio="updateAspectRatio"
+ :deployment-data="store.deploymentData"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <monitoring-state
+ :selected-state="state"
+ :documentation-path="documentationPath"
+ :settings-path="settingsPath"
+ v-else
+ />
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
new file mode 100644
index 00000000000..c376baea79c
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -0,0 +1,297 @@
+<script>
+ /* global Breakpoints */
+ import d3 from 'd3';
+ import monitoringLegends from './monitoring_legends.vue';
+ import monitoringFlag from './monitoring_flag.vue';
+ import monitoringDeployment from './monitoring_deployment.vue';
+ import MonitoringMixin from '../mixins/monitoring_mixins';
+ import eventHub from '../event_hub';
+ import measurements from '../utils/measurements';
+ import { formatRelevantDigits } from '../../lib/utils/number_utils';
+
+ const bisectDate = d3.bisector(d => d.time).left;
+
+ export default {
+ props: {
+ columnData: {
+ type: Object,
+ required: true,
+ },
+ classType: {
+ type: String,
+ required: true,
+ },
+ updateAspectRatio: {
+ type: Boolean,
+ required: true,
+ },
+ deploymentData: {
+ type: Array,
+ required: true,
+ },
+ },
+
+ mixins: [MonitoringMixin],
+
+ data() {
+ return {
+ graphHeight: 450,
+ graphWidth: 600,
+ graphHeightOffset: 120,
+ xScale: {},
+ yScale: {},
+ margin: {},
+ data: [],
+ breakpointHandler: Breakpoints.get(),
+ unitOfDisplay: '',
+ areaColorRgb: '#8fbce8',
+ lineColorRgb: '#1f78d1',
+ yAxisLabel: '',
+ legendTitle: '',
+ reducedDeploymentData: [],
+ area: '',
+ line: '',
+ measurements: measurements.large,
+ currentData: {
+ time: new Date(),
+ value: 0,
+ },
+ currentYCoordinate: 0,
+ currentXCoordinate: 0,
+ currentFlagPosition: 0,
+ metricUsage: '',
+ showFlag: false,
+ showDeployInfo: true,
+ };
+ },
+
+ components: {
+ monitoringLegends,
+ monitoringFlag,
+ monitoringDeployment,
+ },
+
+ computed: {
+ outterViewBox() {
+ return `0 0 ${this.graphWidth} ${this.graphHeight}`;
+ },
+
+ innerViewBox() {
+ if ((this.graphWidth - 150) > 0) {
+ return `0 0 ${this.graphWidth - 150} ${this.graphHeight}`;
+ }
+ return '0 0 0 0';
+ },
+
+ axisTransform() {
+ return `translate(70, ${this.graphHeight - 100})`;
+ },
+
+ paddingBottomRootSvg() {
+ return {
+ paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
+ };
+ },
+ },
+
+ methods: {
+ draw() {
+ const breakpointSize = this.breakpointHandler.getBreakpointSize();
+ const query = this.columnData.queries[0];
+ this.margin = measurements.large.margin;
+ if (breakpointSize === 'xs' || breakpointSize === 'sm') {
+ this.graphHeight = 300;
+ this.margin = measurements.small.margin;
+ this.measurements = measurements.small;
+ }
+ this.data = query.result[0].values;
+ this.unitOfDisplay = query.unit || '';
+ this.yAxisLabel = this.columnData.y_label || 'Values';
+ this.legendTitle = query.label || 'Average';
+ this.graphWidth = this.$refs.baseSvg.clientWidth -
+ this.margin.left - this.margin.right;
+ this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
+ if (this.data !== undefined) {
+ this.renderAxesPaths();
+ this.formatDeployments();
+ }
+ },
+
+ handleMouseOverGraph(e) {
+ let point = this.$refs.graphData.createSVGPoint();
+ point.x = e.clientX;
+ point.y = e.clientY;
+ point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
+ point.x = point.x += 7;
+ const timeValueOverlay = this.xScale.invert(point.x);
+ const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
+ const d0 = this.data[overlayIndex - 1];
+ const d1 = this.data[overlayIndex];
+ if (d0 === undefined || d1 === undefined) return;
+ const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
+ this.currentData = evalTime ? d1 : d0;
+ this.currentXCoordinate = Math.floor(this.xScale(this.currentData.time));
+ const currentDeployXPos = this.mouseOverDeployInfo(point.x);
+ this.currentYCoordinate = this.yScale(this.currentData.value);
+
+ if (this.currentXCoordinate > (this.graphWidth - 200)) {
+ this.currentFlagPosition = this.currentXCoordinate - 103;
+ } else {
+ this.currentFlagPosition = this.currentXCoordinate;
+ }
+
+ if (currentDeployXPos) {
+ this.showFlag = false;
+ } else {
+ this.showFlag = true;
+ }
+
+ this.metricUsage = `${formatRelevantDigits(this.currentData.value)} ${this.unitOfDisplay}`;
+ },
+
+ renderAxesPaths() {
+ const axisXScale = d3.time.scale()
+ .range([0, this.graphWidth]);
+ this.yScale = d3.scale.linear()
+ .range([this.graphHeight - this.graphHeightOffset, 0]);
+ axisXScale.domain(d3.extent(this.data, d => d.time));
+ this.yScale.domain([0, d3.max(this.data.map(d => d.value))]);
+
+ const xAxis = d3.svg.axis()
+ .scale(axisXScale)
+ .ticks(measurements.xTicks)
+ .orient('bottom');
+
+ const yAxis = d3.svg.axis()
+ .scale(this.yScale)
+ .ticks(measurements.yTicks)
+ .orient('left');
+
+ d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
+
+ const width = this.graphWidth;
+ d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
+ .selectAll('.tick')
+ .each(function createTickLines(d, i) {
+ if (i > 0) {
+ d3.select(this).select('line')
+ .attr('x2', width)
+ .attr('class', 'axis-tick');
+ } // Avoid adding the class to the first tick, to prevent coloring
+ }); // This will select all of the ticks once they're rendered
+
+ this.xScale = d3.time.scale()
+ .range([0, this.graphWidth - 70]);
+
+ this.xScale.domain(d3.extent(this.data, d => d.time));
+
+ const areaFunction = d3.svg.area()
+ .x(d => this.xScale(d.time))
+ .y0(this.graphHeight - this.graphHeightOffset)
+ .y1(d => this.yScale(d.value))
+ .interpolate('linear');
+
+ const lineFunction = d3.svg.line()
+ .x(d => this.xScale(d.time))
+ .y(d => this.yScale(d.value));
+
+ this.line = lineFunction(this.data);
+
+ this.area = areaFunction(this.data);
+ },
+ },
+
+ watch: {
+ updateAspectRatio() {
+ if (this.updateAspectRatio) {
+ this.graphHeight = 450;
+ this.graphWidth = 600;
+ this.measurements = measurements.large;
+ this.draw();
+ eventHub.$emit('toggleAspectRatio');
+ }
+ },
+ },
+
+ mounted() {
+ this.draw();
+ },
+ };
+</script>
+<template>
+ <div
+ :class="classType">
+ <h5
+ class="text-center graph-title">
+ {{columnData.title}}
+ </h5>
+ <div
+ class="prometheus-svg-container"
+ :style="paddingBottomRootSvg">
+ <svg
+ :viewBox="outterViewBox"
+ ref="baseSvg">
+ <g
+ class="x-axis"
+ :transform="axisTransform">
+ </g>
+ <g
+ class="y-axis"
+ transform="translate(70, 20)">
+ </g>
+ <monitoring-legends
+ :graph-width="graphWidth"
+ :graph-height="graphHeight"
+ :margin="margin"
+ :measurements="measurements"
+ :area-color-rgb="areaColorRgb"
+ :legend-title="legendTitle"
+ :y-axis-label="yAxisLabel"
+ :metric-usage="metricUsage"
+ />
+ <svg
+ class="graph-data"
+ :viewBox="innerViewBox"
+ ref="graphData">
+ <path
+ class="metric-area"
+ :d="area"
+ :fill="areaColorRgb"
+ transform="translate(-5, 20)">
+ </path>
+ <path
+ class="metric-line"
+ :d="line"
+ :stroke="lineColorRgb"
+ fill="none"
+ stroke-width="2"
+ transform="translate(-5, 20)">
+ </path>
+ <rect
+ class="prometheus-graph-overlay"
+ :width="(graphWidth - 70)"
+ :height="(graphHeight - 100)"
+ transform="translate(-5, 20)"
+ ref="graphOverlay"
+ @mousemove="handleMouseOverGraph($event)">
+ </rect>
+ <monitoring-deployment
+ :show-deploy-info="showDeployInfo"
+ :deployment-data="reducedDeploymentData"
+ :graph-height="graphHeight"
+ :graph-height-offset="graphHeightOffset"
+ />
+ <monitoring-flag
+ v-if="showFlag"
+ :current-x-coordinate="currentXCoordinate"
+ :current-y-coordinate="currentYCoordinate"
+ :current-data="currentData"
+ :current-flag-position="currentFlagPosition"
+ :graph-height="graphHeight"
+ :graph-height-offset="graphHeightOffset"
+ />
+ </svg>
+ </svg>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_deployment.vue b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue
new file mode 100644
index 00000000000..e6432ba3191
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue
@@ -0,0 +1,136 @@
+<script>
+ import {
+ dateFormat,
+ timeFormat,
+ } from '../constants';
+
+ export default {
+ props: {
+ showDeployInfo: {
+ type: Boolean,
+ required: true,
+ },
+ deploymentData: {
+ type: Array,
+ required: true,
+ },
+ graphHeight: {
+ type: Number,
+ required: true,
+ },
+ graphHeightOffset: {
+ type: Number,
+ required: true,
+ },
+ },
+
+ computed: {
+ calculatedHeight() {
+ return this.graphHeight - this.graphHeightOffset;
+ },
+ },
+
+ methods: {
+ refText(d) {
+ return d.tag ? d.ref : d.sha.slice(0, 6);
+ },
+
+ formatTime(deploymentTime) {
+ return timeFormat(deploymentTime);
+ },
+
+ formatDate(deploymentTime) {
+ return dateFormat(deploymentTime);
+ },
+
+ nameDeploymentClass(deployment) {
+ return `deploy-info-${deployment.id}`;
+ },
+
+ transformDeploymentGroup(deployment) {
+ return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
+ },
+ },
+ };
+</script>
+<template>
+ <g
+ class="deploy-info"
+ v-if="showDeployInfo">
+ <g
+ v-for="(deployment, index) in deploymentData"
+ :key="index"
+ :class="nameDeploymentClass(deployment)"
+ :transform="transformDeploymentGroup(deployment)">
+ <rect
+ x="0"
+ y="0"
+ :height="calculatedHeight"
+ width="3"
+ fill="url(#shadow-gradient)">
+ </rect>
+ <line
+ class="deployment-line"
+ x1="0"
+ y1="0"
+ x2="0"
+ :y2="calculatedHeight"
+ stroke="#000">
+ </line>
+ <svg
+ v-if="deployment.showDeploymentFlag"
+ class="js-deploy-info-box"
+ x="3"
+ y="0"
+ width="92"
+ height="60">
+ <rect
+ class="rect-text-metric deploy-info-rect rect-metric"
+ x="1"
+ y="1"
+ rx="2"
+ width="90"
+ height="58">
+ </rect>
+ <g
+ transform="translate(5, 2)">
+ <text
+ class="deploy-info-text text-metric-bold">
+ {{refText(deployment)}}
+ </text>
+ </g>
+ <text
+ class="deploy-info-text"
+ y="18"
+ transform="translate(5, 2)">
+ {{formatDate(deployment.time)}}
+ </text>
+ <text
+ class="deploy-info-text text-metric-bold"
+ y="38"
+ transform="translate(5, 2)">
+ {{formatTime(deployment.time)}}
+ </text>
+ </svg>
+ </g>
+ <svg
+ height="0"
+ width="0">
+ <defs>
+ <linearGradient
+ id="shadow-gradient">
+ <stop
+ offset="0%"
+ stop-color="#000"
+ stop-opacity="0.4">
+ </stop>
+ <stop
+ offset="100%"
+ stop-color="#000"
+ stop-opacity="0">
+ </stop>
+ </linearGradient>
+ </defs>
+ </svg>
+ </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
new file mode 100644
index 00000000000..5a0e50fcab3
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
@@ -0,0 +1,104 @@
+<script>
+ import {
+ dateFormat,
+ timeFormat,
+ } from '../constants';
+
+ export default {
+ props: {
+ currentXCoordinate: {
+ type: Number,
+ required: true,
+ },
+ currentYCoordinate: {
+ type: Number,
+ required: true,
+ },
+ currentFlagPosition: {
+ type: Number,
+ required: true,
+ },
+ currentData: {
+ type: Object,
+ required: true,
+ },
+ graphHeight: {
+ type: Number,
+ required: true,
+ },
+ graphHeightOffset: {
+ type: Number,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ circleColorRgb: '#8fbce8',
+ };
+ },
+
+ computed: {
+ formatTime() {
+ return timeFormat(this.currentData.time);
+ },
+
+ formatDate() {
+ return dateFormat(this.currentData.time);
+ },
+
+ calculatedHeight() {
+ return this.graphHeight - this.graphHeightOffset;
+ },
+ },
+ };
+</script>
+<template>
+ <g class="mouse-over-flag">
+ <line
+ class="selected-metric-line"
+ :x1="currentXCoordinate"
+ :y1="0"
+ :x2="currentXCoordinate"
+ :y2="calculatedHeight"
+ transform="translate(-5, 20)">
+ </line>
+ <circle
+ class="circle-metric"
+ :fill="circleColorRgb"
+ stroke="#000"
+ :cx="currentXCoordinate"
+ :cy="currentYCoordinate"
+ r="5"
+ transform="translate(-5, 20)">
+ </circle>
+ <svg
+ class="rect-text-metric"
+ :x="currentFlagPosition"
+ y="0">
+ <rect
+ class="rect-metric"
+ x="4"
+ y="1"
+ rx="2"
+ width="90"
+ height="40"
+ transform="translate(-3, 20)">
+ </rect>
+ <text
+ class="text-metric text-metric-bold"
+ x="16"
+ y="35"
+ transform="translate(-5, 20)">
+ {{formatTime}}
+ </text>
+ <text
+ class="text-metric"
+ x="16"
+ y="15"
+ transform="translate(-5, 20)">
+ {{formatDate}}
+ </text>
+ </svg>
+ </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
new file mode 100644
index 00000000000..922a5e1bf0e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
@@ -0,0 +1,144 @@
+<script>
+ export default {
+ props: {
+ graphWidth: {
+ type: Number,
+ required: true,
+ },
+ graphHeight: {
+ type: Number,
+ required: true,
+ },
+ margin: {
+ type: Object,
+ required: true,
+ },
+ measurements: {
+ type: Object,
+ required: true,
+ },
+ areaColorRgb: {
+ type: String,
+ required: true,
+ },
+ legendTitle: {
+ type: String,
+ required: true,
+ },
+ yAxisLabel: {
+ type: String,
+ required: true,
+ },
+ metricUsage: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ yLabelWidth: 0,
+ yLabelHeight: 0,
+ };
+ },
+ computed: {
+ textTransform() {
+ const yCoordinate = (((this.graphHeight - this.margin.top)
+ + this.measurements.axisLabelLineOffset) / 2) || 0;
+
+ return `translate(15, ${yCoordinate}) rotate(-90)`;
+ },
+
+ rectTransform() {
+ const yCoordinate = ((this.graphHeight - this.margin.top) / 2)
+ + (this.yLabelWidth / 2) + 10 || 0;
+
+ return `translate(0, ${yCoordinate}) rotate(-90)`;
+ },
+
+ xPosition() {
+ return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2)
+ - this.margin.right) || 0;
+ },
+
+ yPosition() {
+ return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
+ },
+ },
+ mounted() {
+ this.$nextTick(() => {
+ const bbox = this.$refs.ylabel.getBBox();
+ this.yLabelWidth = bbox.width + 10; // Added some padding
+ this.yLabelHeight = bbox.height + 5;
+ });
+ },
+ };
+</script>
+<template>
+ <g
+ class="axis-label-container">
+ <line
+ class="label-x-axis-line"
+ stroke="#000000"
+ stroke-width="1"
+ x1="10"
+ :y1="yPosition"
+ :x2="graphWidth + 20"
+ :y2="yPosition">
+ </line>
+ <line
+ class="label-y-axis-line"
+ stroke="#000000"
+ stroke-width="1"
+ x1="10"
+ y1="0"
+ :x2="10"
+ :y2="yPosition">
+ </line>
+ <rect
+ class="rect-axis-text"
+ :transform="rectTransform"
+ :width="yLabelWidth"
+ :height="yLabelHeight">
+ </rect>
+ <text
+ class="label-axis-text y-label-text"
+ text-anchor="middle"
+ :transform="textTransform"
+ ref="ylabel">
+ {{yAxisLabel}}
+ </text>
+ <rect
+ class="rect-axis-text"
+ :x="xPosition + 60"
+ :y="graphHeight - 80"
+ width="35"
+ height="50">
+ </rect>
+ <text
+ class="label-axis-text x-label-text"
+ :x="xPosition + 60"
+ :y="yPosition"
+ dy=".35em">
+ Time
+ </text>
+ <rect
+ :fill="areaColorRgb"
+ :width="measurements.legends.width"
+ :height="measurements.legends.height"
+ x="20"
+ :y="graphHeight - measurements.legendOffset">
+ </rect>
+ <text
+ class="text-metric-title"
+ x="50"
+ :y="graphHeight - 25">
+ {{legendTitle}}
+ </text>
+ <text
+ class="text-metric-usage"
+ x="50"
+ :y="graphHeight - 10">
+ {{metricUsage}}
+ </text>
+ </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_row.vue b/app/assets/javascripts/monitoring/components/monitoring_row.vue
new file mode 100644
index 00000000000..e5528f17880
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_row.vue
@@ -0,0 +1,41 @@
+<script>
+ import monitoringColumn from './monitoring_column.vue';
+
+ export default {
+ props: {
+ rowData: {
+ type: Array,
+ required: true,
+ },
+ updateAspectRatio: {
+ type: Boolean,
+ required: true,
+ },
+ deploymentData: {
+ type: Array,
+ required: true,
+ },
+ },
+ components: {
+ monitoringColumn,
+ },
+ computed: {
+ bootstrapClass() {
+ return this.rowData.length >= 2 ? 'col-md-6' : 'col-md-12';
+ },
+ },
+ };
+</script>
+<template>
+ <div
+ class="prometheus-row row">
+ <monitoring-column
+ v-for="(column, index) in rowData"
+ :column-data="column"
+ :class-type="bootstrapClass"
+ :key="index"
+ :update-aspect-ratio="updateAspectRatio"
+ :deployment-data="deploymentData"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_state.vue b/app/assets/javascripts/monitoring/components/monitoring_state.vue
new file mode 100644
index 00000000000..598021aa4df
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_state.vue
@@ -0,0 +1,112 @@
+<script>
+ import gettingStartedSvg from 'empty_states/monitoring/_getting_started.svg';
+ import loadingSvg from 'empty_states/monitoring/_loading.svg';
+ import unableToConnectSvg from 'empty_states/monitoring/_unable_to_connect.svg';
+
+ export default {
+ props: {
+ documentationPath: {
+ type: String,
+ required: true,
+ },
+ settingsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ selectedState: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ states: {
+ gettingStarted: {
+ svg: gettingStartedSvg,
+ title: 'Get started with performance monitoring',
+ description: 'Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.',
+ buttonText: 'Configure Prometheus',
+ },
+ loading: {
+ svg: loadingSvg,
+ title: 'Waiting for performance data',
+ description: 'Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.',
+ buttonText: 'View documentation',
+ },
+ unableToConnect: {
+ svg: unableToConnectSvg,
+ title: 'Unable to connect to Prometheus server',
+ description: 'Ensure connectivity is available from the GitLab server to the ',
+ buttonText: 'View documentation',
+ },
+ },
+ };
+ },
+ computed: {
+ currentState() {
+ return this.states[this.selectedState];
+ },
+
+ buttonPath() {
+ if (this.selectedState === 'gettingStarted') {
+ return this.settingsPath;
+ }
+ return this.documentationPath;
+ },
+
+ showButtonDescription() {
+ if (this.selectedState === 'unableToConnect') return true;
+ return false;
+ },
+ },
+ };
+</script>
+<template>
+ <div
+ class="prometheus-state">
+ <div
+ class="row">
+ <div
+ class="col-md-4 col-md-offset-4 state-svg"
+ v-html="currentState.svg">
+ </div>
+ </div>
+ <div
+ class="row">
+ <div
+ class="col-md-6 col-md-offset-3">
+ <h4
+ class="text-center state-title">
+ {{currentState.title}}
+ </h4>
+ </div>
+ </div>
+ <div
+ class="row">
+ <div
+ class="col-md-6 col-md-offset-3">
+ <div
+ class="description-text text-center state-description">
+ {{currentState.description}}
+ <a
+ :href="settingsPath"
+ v-if="showButtonDescription">
+ Prometheus server
+ </a>
+ </div>
+ </div>
+ </div>
+ <div
+ class="row state-button-section">
+ <div
+ class="col-md-4 col-md-offset-4 text-center state-button">
+ <a
+ class="btn btn-success"
+ :href="buttonPath">
+ {{currentState.buttonText}}
+ </a>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/deployments.js b/app/assets/javascripts/monitoring/deployments.js
deleted file mode 100644
index fc92ab61b31..00000000000
--- a/app/assets/javascripts/monitoring/deployments.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/* global Flash */
-import d3 from 'd3';
-import {
- dateFormat,
- timeFormat,
-} from './constants';
-
-export default class Deployments {
- constructor(width, height) {
- this.width = width;
- this.height = height;
-
- this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
-
- this.createGradientDef();
- }
-
- init(chartData) {
- this.chartData = chartData;
-
- this.x = d3.time.scale().range([0, this.width]);
- this.x.domain(d3.extent(this.chartData, d => d.time));
-
- this.charts = d3.selectAll('.prometheus-graph');
-
- this.getData();
- }
-
- getData() {
- $.ajax({
- url: this.endpoint,
- dataType: 'JSON',
- })
- .fail(() => new Flash('Error getting deployment information.'))
- .done((data) => {
- this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
- const time = new Date(deployment.created_at);
- const xPos = Math.floor(this.x(time));
-
- time.setSeconds(this.chartData[0].time.getSeconds());
-
- if (xPos >= 0) {
- deploymentDataArray.push({
- id: deployment.id,
- time,
- sha: deployment.sha,
- tag: deployment.tag,
- ref: deployment.ref.name,
- xPos,
- });
- }
-
- return deploymentDataArray;
- }, []);
-
- this.plotData();
- });
- }
-
- plotData() {
- this.charts.each((d, i) => {
- const svg = d3.select(this.charts[0][i]);
- const chart = svg.select('.graph-container');
- const key = svg.node().getAttribute('graph-type');
-
- this.createLine(chart, key);
- this.createDeployInfoBox(chart, key);
- });
- }
-
- createGradientDef() {
- const defs = d3.select('body')
- .append('svg')
- .attr({
- height: 0,
- width: 0,
- })
- .append('defs');
-
- defs.append('linearGradient')
- .attr({
- id: 'shadow-gradient',
- })
- .append('stop')
- .attr({
- offset: '0%',
- 'stop-color': '#000',
- 'stop-opacity': 0.4,
- })
- .select(this.selectParentNode)
- .append('stop')
- .attr({
- offset: '100%',
- 'stop-color': '#000',
- 'stop-opacity': 0,
- });
- }
-
- createLine(chart, key) {
- chart.append('g')
- .attr({
- class: 'deploy-info',
- })
- .selectAll('.deploy-info')
- .data(this.data)
- .enter()
- .append('g')
- .attr({
- class: d => `deploy-info-${d.id}-${key}`,
- transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
- })
- .append('rect')
- .attr({
- x: 1,
- y: 0,
- height: this.height + 1,
- width: 3,
- fill: 'url(#shadow-gradient)',
- })
- .select(this.selectParentNode)
- .append('line')
- .attr({
- class: 'deployment-line',
- x1: 0,
- x2: 0,
- y1: 0,
- y2: this.height + 1,
- });
- }
-
- createDeployInfoBox(chart, key) {
- chart.selectAll('.deploy-info')
- .selectAll('.js-deploy-info-box')
- .data(this.data)
- .enter()
- .select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
- .append('svg')
- .attr({
- class: 'js-deploy-info-box hidden',
- x: 3,
- y: 0,
- width: 92,
- height: 60,
- })
- .append('rect')
- .attr({
- class: 'rect-text-metric deploy-info-rect rect-metric',
- x: 1,
- y: 1,
- rx: 2,
- width: 90,
- height: 58,
- })
- .select(this.selectParentNode)
- .append('g')
- .attr({
- transform: 'translate(5, 2)',
- })
- .append('text')
- .attr({
- class: 'deploy-info-text text-metric-bold',
- })
- .text(Deployments.refText)
- .select(this.selectParentNode)
- .append('text')
- .attr({
- class: 'deploy-info-text',
- y: 18,
- })
- .text(d => dateFormat(d.time))
- .select(this.selectParentNode)
- .append('text')
- .attr({
- class: 'deploy-info-text text-metric-bold',
- y: 38,
- })
- .text(d => timeFormat(d.time));
- }
-
- static toggleDeployTextbox(deploy, key, showInfoBox) {
- d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
- .classed('hidden', !showInfoBox);
- }
-
- mouseOverDeployInfo(mouseXPos, key) {
- if (!this.data) return false;
-
- let dataFound = false;
-
- this.data.forEach((d) => {
- if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
- dataFound = d.xPos + 1;
-
- Deployments.toggleDeployTextbox(d, key, true);
- } else {
- Deployments.toggleDeployTextbox(d, key, false);
- }
- });
-
- return dataFound;
- }
-
- /* `this` is bound to the D3 node */
- selectParentNode() {
- return this.parentNode;
- }
-
- static refText(d) {
- return d.tag ? d.ref : d.sha.slice(0, 6);
- }
-}
diff --git a/app/assets/javascripts/monitoring/event_hub.js b/app/assets/javascripts/monitoring/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/monitoring/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
new file mode 100644
index 00000000000..8e62fa63f13
--- /dev/null
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -0,0 +1,46 @@
+const mixins = {
+ methods: {
+ mouseOverDeployInfo(mouseXPos) {
+ if (!this.reducedDeploymentData) return false;
+
+ let dataFound = false;
+ this.reducedDeploymentData = this.reducedDeploymentData.map((d) => {
+ const deployment = d;
+ if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
+ dataFound = d.xPos + 1;
+
+ deployment.showDeploymentFlag = true;
+ } else {
+ deployment.showDeploymentFlag = false;
+ }
+ return deployment;
+ });
+
+ return dataFound;
+ },
+ formatDeployments() {
+ this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
+ const time = new Date(deployment.created_at);
+ const xPos = Math.floor(this.xScale(time));
+
+ time.setSeconds(this.data[0].time.getSeconds());
+
+ if (xPos >= 0) {
+ deploymentDataArray.push({
+ id: deployment.id,
+ time,
+ sha: deployment.sha,
+ tag: deployment.tag,
+ ref: deployment.ref.name,
+ xPos,
+ showDeploymentFlag: false,
+ });
+ }
+
+ return deploymentDataArray;
+ }, []);
+ },
+ },
+};
+
+export default mixins;
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index b3ce9310417..5d5cb56af72 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,6 +1,10 @@
-import PrometheusGraph from './prometheus_graph';
+import Vue from 'vue';
+import Monitoring from './components/monitoring.vue';
-document.addEventListener('DOMContentLoaded', function onLoad() {
- document.removeEventListener('DOMContentLoaded', onLoad, false);
- return new PrometheusGraph();
-}, false);
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#prometheus-graphs',
+ components: {
+ 'monitoring-dashboard': Monitoring,
+ },
+ render: createElement => createElement('monitoring-dashboard'),
+}));
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
deleted file mode 100644
index 6af88769129..00000000000
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ /dev/null
@@ -1,433 +0,0 @@
-/* eslint-disable no-new */
-/* global Flash */
-
-import d3 from 'd3';
-import statusCodes from '~/lib/utils/http_status';
-import Deployments from './deployments';
-import '../lib/utils/common_utils';
-import { formatRelevantDigits } from '../lib/utils/number_utils';
-import '../flash';
-import {
- dateFormat,
- timeFormat,
-} from './constants';
-
-const prometheusContainer = '.prometheus-container';
-const prometheusParentGraphContainer = '.prometheus-graphs';
-const prometheusGraphsContainer = '.prometheus-graph';
-const prometheusStatesContainer = '.prometheus-state';
-const metricsEndpoint = 'metrics.json';
-const bisectDate = d3.bisector(d => d.time).left;
-const extraAddedWidthParent = 100;
-
-class PrometheusGraph {
- constructor() {
- const $prometheusContainer = $(prometheusContainer);
- const hasMetrics = $prometheusContainer.data('has-metrics');
- this.docLink = $prometheusContainer.data('doc-link');
- this.integrationLink = $prometheusContainer.data('prometheus-integration');
- this.state = '';
-
- $(document).ajaxError(() => {});
-
- if (hasMetrics) {
- this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
- this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
- const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
- extraAddedWidthParent;
- this.originalWidth = parentContainerWidth;
- this.originalHeight = 330;
- this.width = parentContainerWidth - this.margin.left - this.margin.right;
- this.height = this.originalHeight - this.margin.top - this.margin.bottom;
- this.backOffRequestCounter = 0;
- this.deployments = new Deployments(this.width, this.height);
- this.configureGraph();
- this.init();
- } else {
- const prevState = this.state;
- this.state = '.js-getting-started';
- this.updateState(prevState);
- }
- }
-
- createGraph() {
- Object.keys(this.graphSpecificProperties).forEach((key) => {
- const value = this.graphSpecificProperties[key];
- if (value.data.length > 0) {
- this.plotValues(key);
- }
- });
- }
-
- init() {
- return this.getData().then((metricsResponse) => {
- let enoughData = true;
- if (typeof metricsResponse === 'undefined') {
- enoughData = false;
- } else {
- Object.keys(metricsResponse.metrics).forEach((key) => {
- if (key === 'cpu_values' || key === 'memory_values') {
- const currentData = (metricsResponse.metrics[key])[0];
- if (currentData.values.length <= 2) {
- enoughData = false;
- }
- }
- });
- }
- if (enoughData) {
- $(prometheusStatesContainer).hide();
- $(prometheusParentGraphContainer).show();
- this.transformData(metricsResponse);
- this.createGraph();
-
- const firstMetricData = this.graphSpecificProperties[
- Object.keys(this.graphSpecificProperties)[0]
- ].data;
-
- this.deployments.init(firstMetricData);
- }
- });
- }
-
- plotValues(key) {
- const graphSpecifics = this.graphSpecificProperties[key];
-
- const x = d3.time.scale()
- .range([0, this.width]);
-
- const y = d3.scale.linear()
- .range([this.height, 0]);
-
- graphSpecifics.xScale = x;
- graphSpecifics.yScale = y;
-
- const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
-
- const chart = d3.select(prometheusGraphContainer)
- .attr('width', this.width + this.margin.left + this.margin.right)
- .attr('height', this.height + this.margin.bottom + this.margin.top)
- .append('g')
- .attr('class', 'graph-container')
- .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
-
- const axisLabelContainer = d3.select(prometheusGraphContainer)
- .attr('width', this.originalWidth)
- .attr('height', this.originalHeight)
- .append('g')
- .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
-
- x.domain(d3.extent(graphSpecifics.data, d => d.time));
- y.domain([0, d3.max(graphSpecifics.data.map(metricValue => metricValue.value))]);
-
- const xAxis = d3.svg.axis()
- .scale(x)
- .ticks(this.commonGraphProperties.axis_no_ticks)
- .orient('bottom');
-
- const yAxis = d3.svg.axis()
- .scale(y)
- .ticks(this.commonGraphProperties.axis_no_ticks)
- .tickSize(-this.width)
- .outerTickSize(0)
- .orient('left');
-
- this.createAxisLabelContainers(axisLabelContainer, key);
-
- chart.append('g')
- .attr('class', 'x-axis')
- .attr('transform', `translate(0,${this.height})`)
- .call(xAxis);
-
- chart.append('g')
- .attr('class', 'y-axis')
- .call(yAxis);
-
- const area = d3.svg.area()
- .x(d => x(d.time))
- .y0(this.height)
- .y1(d => y(d.value))
- .interpolate('linear');
-
- const line = d3.svg.line()
- .x(d => x(d.time))
- .y(d => y(d.value));
-
- chart.append('path')
- .datum(graphSpecifics.data)
- .attr('d', area)
- .attr('class', 'metric-area')
- .attr('fill', graphSpecifics.area_fill_color);
-
- chart.append('path')
- .datum(graphSpecifics.data)
- .attr('class', 'metric-line')
- .attr('stroke', graphSpecifics.line_color)
- .attr('fill', 'none')
- .attr('stroke-width', this.commonGraphProperties.area_stroke_width)
- .attr('d', line);
-
- // Overlay area for the mouseover events
- chart.append('rect')
- .attr('class', 'prometheus-graph-overlay')
- .attr('width', this.width)
- .attr('height', this.height)
- .on('mousemove', this.handleMouseOverGraph.bind(this, prometheusGraphContainer));
- }
-
- // The legends from the metric
- createAxisLabelContainers(axisLabelContainer, key) {
- const graphSpecifics = this.graphSpecificProperties[key];
-
- axisLabelContainer.append('line')
- .attr('class', 'label-x-axis-line')
- .attr('stroke', '#000000')
- .attr('stroke-width', '1')
- .attr({
- x1: 10,
- y1: this.originalHeight - this.margin.top,
- x2: (this.originalWidth - this.margin.right) + 10,
- y2: this.originalHeight - this.margin.top,
- });
-
- axisLabelContainer.append('line')
- .attr('class', 'label-y-axis-line')
- .attr('stroke', '#000000')
- .attr('stroke-width', '1')
- .attr({
- x1: 10,
- y1: 0,
- x2: 10,
- y2: this.originalHeight - this.margin.top,
- });
-
- axisLabelContainer.append('rect')
- .attr('class', 'rect-axis-text')
- .attr('x', 0)
- .attr('y', 50)
- .attr('width', 30)
- .attr('height', 150);
-
- axisLabelContainer.append('text')
- .attr('class', 'label-axis-text')
- .attr('text-anchor', 'middle')
- .attr('transform', `translate(15, ${(this.originalHeight - this.margin.top) / 2}) rotate(-90)`)
- .text(graphSpecifics.graph_legend_title);
-
- axisLabelContainer.append('rect')
- .attr('class', 'rect-axis-text')
- .attr('x', (this.originalWidth / 2) - this.margin.right)
- .attr('y', this.originalHeight - 100)
- .attr('width', 30)
- .attr('height', 80);
-
- axisLabelContainer.append('text')
- .attr('class', 'label-axis-text')
- .attr('x', (this.originalWidth / 2) - this.margin.right)
- .attr('y', this.originalHeight - this.margin.top)
- .attr('dy', '.35em')
- .text('Time');
-
- // Legends
-
- // Metric Usage
- axisLabelContainer.append('rect')
- .attr('x', this.originalWidth - 170)
- .attr('y', (this.originalHeight / 2) - 60)
- .style('fill', graphSpecifics.area_fill_color)
- .attr('width', 20)
- .attr('height', 35);
-
- axisLabelContainer.append('text')
- .attr('class', 'text-metric-title')
- .attr('x', this.originalWidth - 140)
- .attr('y', (this.originalHeight / 2) - 50)
- .text('Average');
-
- axisLabelContainer.append('text')
- .attr('class', 'text-metric-usage')
- .attr('x', this.originalWidth - 140)
- .attr('y', (this.originalHeight / 2) - 25);
- }
-
- handleMouseOverGraph(prometheusGraphContainer) {
- const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`);
- const currentXCoordinate = d3.mouse(rectOverlay)[0];
-
- Object.keys(this.graphSpecificProperties).forEach((key) => {
- const currentGraphProps = this.graphSpecificProperties[key];
- const timeValueOverlay = currentGraphProps.xScale.invert(currentXCoordinate);
- const overlayIndex = bisectDate(currentGraphProps.data, timeValueOverlay, 1);
- const d0 = currentGraphProps.data[overlayIndex - 1];
- const d1 = currentGraphProps.data[overlayIndex];
- const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
- const currentData = evalTime ? d1 : d0;
- const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
- const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
- const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
- const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
- const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
-
- // Clear up all the pieces of the flag
- d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
- d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
- d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
-
- const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
- currentChart.append('line')
- .attr({
- class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
- x1: currentTimeCoordinate,
- y1: currentGraphProps.yScale(0),
- x2: currentTimeCoordinate,
- y2: maxMetricValue,
- });
-
- currentChart.append('circle')
- .attr('class', 'circle-metric')
- .attr('fill', currentGraphProps.line_color)
- .attr('cx', currentDeployXPos || currentTimeCoordinate)
- .attr('cy', currentGraphProps.yScale(currentData.value))
- .attr('r', this.commonGraphProperties.circle_radius_metric);
-
- if (currentDeployXPos) return;
-
- // The little box with text
- const rectTextMetric = currentChart.append('svg')
- .attr({
- class: 'rect-text-metric',
- x: currentTimeCoordinate,
- y: 0,
- });
-
- rectTextMetric.append('rect')
- .attr({
- class: 'rect-metric',
- x: 4,
- y: 1,
- rx: 2,
- width: this.commonGraphProperties.rect_text_width,
- height: this.commonGraphProperties.rect_text_height,
- });
-
- rectTextMetric.append('text')
- .attr({
- class: 'text-metric text-metric-bold',
- x: 8,
- y: 35,
- })
- .text(timeFormat(currentData.time));
-
- rectTextMetric.append('text')
- .attr({
- class: 'text-metric-date',
- x: 8,
- y: 15,
- })
- .text(dateFormat(currentData.time));
-
- let currentMetricValue = formatRelevantDigits(currentData.value);
- if (key === 'cpu_values') {
- currentMetricValue = `${currentMetricValue}%`;
- } else {
- currentMetricValue = `${currentMetricValue} MB`;
- }
-
- d3.select(`${currentPrometheusGraphContainer} .text-metric-usage`)
- .text(currentMetricValue);
- });
- }
-
- configureGraph() {
- this.graphSpecificProperties = {
- cpu_values: {
- area_fill_color: '#edf3fc',
- line_color: '#5b99f7',
- graph_legend_title: 'CPU Usage (Cores)',
- data: [],
- xScale: {},
- yScale: {},
- },
- memory_values: {
- area_fill_color: '#fca326',
- line_color: '#fc6d26',
- graph_legend_title: 'Memory Usage (MB)',
- data: [],
- xScale: {},
- yScale: {},
- },
- };
-
- this.commonGraphProperties = {
- area_stroke_width: 2,
- median_total_characters: 8,
- circle_radius_metric: 5,
- rect_text_width: 90,
- rect_text_height: 40,
- axis_no_ticks: 3,
- };
- }
-
- getData() {
- const maxNumberOfRequests = 3;
- this.state = '.js-loading';
- this.updateState();
- return gl.utils.backOff((next, stop) => {
- $.ajax({
- url: metricsEndpoint,
- dataType: 'json',
- })
- .done((data, statusText, resp) => {
- if (resp.status === statusCodes.NO_CONTENT) {
- this.backOffRequestCounter = this.backOffRequestCounter += 1;
- if (this.backOffRequestCounter < maxNumberOfRequests) {
- next();
- } else if (this.backOffRequestCounter >= maxNumberOfRequests) {
- stop(new Error('loading'));
- }
- } else if (!data.success) {
- stop(new Error('loading'));
- } else {
- stop({
- status: resp.status,
- metrics: data,
- });
- }
- }).fail(stop);
- })
- .then((resp) => {
- if (resp.status === statusCodes.NO_CONTENT) {
- return {};
- }
- return resp.metrics;
- })
- .catch(() => {
- const prevState = this.state;
- this.state = '.js-unable-to-connect';
- this.updateState(prevState);
- });
- }
-
- transformData(metricsResponse) {
- Object.keys(metricsResponse.metrics).forEach((key) => {
- if (key === 'cpu_values' || key === 'memory_values') {
- const metricValues = (metricsResponse.metrics[key])[0];
- this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
- time: new Date(metric[0] * 1000),
- value: metric[1],
- }));
- }
- });
- }
-
- updateState(prevState) {
- const $statesContainer = $(prometheusStatesContainer);
- $(prometheusParentGraphContainer).hide();
- if (prevState) {
- $(`${prevState}`, $statesContainer).addClass('hidden');
- }
- $(`${this.state}`, $statesContainer).removeClass('hidden');
- $(prometheusStatesContainer).show();
- }
-}
-
-export default PrometheusGraph;
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
new file mode 100644
index 00000000000..1e9ae934853
--- /dev/null
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class MonitoringService {
+ constructor(endpoint) {
+ this.graphs = Vue.resource(endpoint);
+ }
+
+ get() {
+ return this.graphs.get();
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ getDeploymentData(endpoint) {
+ return Vue.http.get(endpoint);
+ }
+}
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
new file mode 100644
index 00000000000..737c964f12e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -0,0 +1,61 @@
+import _ from 'underscore';
+
+class MonitoringStore {
+ constructor() {
+ this.groups = [];
+ this.deploymentData = [];
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ createArrayRows(metrics = []) {
+ const currentMetrics = metrics;
+ const availableMetrics = [];
+ let metricsRow = [];
+ let index = 1;
+ Object.keys(currentMetrics).forEach((key) => {
+ const metricValues = currentMetrics[key].queries[0].result[0].values;
+ if (metricValues != null) {
+ const literalMetrics = metricValues.map(metric => ({
+ time: new Date(metric[0] * 1000),
+ value: metric[1],
+ }));
+ currentMetrics[key].queries[0].result[0].values = literalMetrics;
+ metricsRow.push(currentMetrics[key]);
+ if (index % 2 === 0) {
+ availableMetrics.push(metricsRow);
+ metricsRow = [];
+ }
+ index = index += 1;
+ }
+ });
+ if (metricsRow.length > 0) {
+ availableMetrics.push(metricsRow);
+ }
+ return availableMetrics;
+ }
+
+ storeMetrics(groups = []) {
+ this.groups = groups.map((group) => {
+ const currentGroup = group;
+ currentGroup.metrics = _.chain(group.metrics).sortBy('weight').sortBy('title').value();
+ currentGroup.metrics = this.createArrayRows(currentGroup.metrics);
+ return currentGroup;
+ });
+ }
+
+ storeDeploymentData(deploymentData = []) {
+ this.deploymentData = deploymentData;
+ }
+
+ getMetricsCount() {
+ let metricsCount = 0;
+ this.groups.forEach((group) => {
+ group.metrics.forEach((metric) => {
+ metricsCount = metricsCount += metric.length;
+ });
+ });
+ return metricsCount;
+ }
+}
+
+export default MonitoringStore;
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
new file mode 100644
index 00000000000..62cd19c86e1
--- /dev/null
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -0,0 +1,40 @@
+export default {
+ small: { // Covers both xs and sm screen sizes
+ margin: {
+ top: 40,
+ right: 40,
+ bottom: 50,
+ left: 40,
+ },
+ legends: {
+ width: 15,
+ height: 25,
+ },
+ backgroundLegend: {
+ width: 30,
+ height: 50,
+ },
+ axisLabelLineOffset: -20,
+ legendOffset: 35,
+ },
+ large: { // This covers both md and lg screen sizes
+ margin: {
+ top: 80,
+ right: 80,
+ bottom: 100,
+ left: 80,
+ },
+ legends: {
+ width: 20,
+ height: 30,
+ },
+ backgroundLegend: {
+ width: 30,
+ height: 150,
+ },
+ axisLabelLineOffset: 20,
+ legendOffset: 38,
+ },
+ xTicks: 8,
+ yTicks: 3,
+};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b21d7774920..1a68c5bca00 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -18,9 +18,10 @@ import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import CommentTypeToggle from './comment_type_toggle';
+import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
-import './task_list';
+import TaskList from './task_list';
window.autosize = autosize;
window.Dropzone = Dropzone;
@@ -70,7 +71,7 @@ export default class Notes {
this.addBinding();
this.setPollingInterval();
this.setupMainTargetNoteForm();
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes'
@@ -291,8 +292,13 @@ export default class Notes {
if ('emoji_award' in noteEntity.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
- gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
- return gl.awardsHandler.scrollToAwards();
+
+ loadAwardsHandler().then((awardsHandler) => {
+ awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
+ awardsHandler.scrollToAwards();
+ }).catch(() => {
+ // ignore
+ });
}
}
}
@@ -337,6 +343,10 @@ export default class Notes {
if (!noteEntity.valid) {
if (noteEntity.errors.commands_only) {
+ if (noteEntity.commands_changes &&
+ Object.keys(noteEntity.commands_changes).length > 0) {
+ $notesList.find('.system-note.being-posted').remove();
+ }
this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
}
@@ -829,6 +839,8 @@ export default class Notes {
*/
setupDiscussionNoteForm(dataHolder, form) {
// setup note target
+ const diffFileData = dataHolder.closest('.text-file');
+
var discussionID = dataHolder.data('discussionId');
if (discussionID) {
@@ -839,9 +851,10 @@ export default class Notes {
form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType'));
- form.find('#note_noteable_type').val(dataHolder.data('noteableType'));
- form.find('#note_noteable_id').val(dataHolder.data('noteableId'));
- form.find('#note_commit_id').val(dataHolder.data('commitId'));
+ form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
+ form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
+ form.find('#note_commit_id').val(diffFileData.data('commitId'));
+
form.find('#note_type').val(dataHolder.data('noteType'));
// LegacyDiffNote
@@ -1485,7 +1498,7 @@ export default class Notes {
const cachedNoteBodyText = $noteBodyText.html();
// Show updated comment content temporarily
- $noteBodyText.html(_.escape(formContent));
+ $noteBodyText.html(formContent);
$editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
$editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
diff --git a/app/assets/javascripts/oauth_remember_me.js b/app/assets/javascripts/oauth_remember_me.js
new file mode 100644
index 00000000000..ffc2dd6bbca
--- /dev/null
+++ b/app/assets/javascripts/oauth_remember_me.js
@@ -0,0 +1,32 @@
+/**
+ * OAuth-based login buttons have a separate "remember me" checkbox.
+ *
+ * Toggling this checkbox adds/removes a `remember_me` parameter to the
+ * login buttons' href, which is passed on to the omniauth callback.
+ **/
+
+export default class OAuthRememberMe {
+ constructor(opts = {}) {
+ this.container = opts.container || '';
+ this.loginLinkSelector = '.oauth-login';
+ }
+
+ bindEvents() {
+ $('#remember_me', this.container).on('click', this.toggleRememberMe);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ toggleRememberMe(event) {
+ const rememberMe = $(event.target).is(':checked');
+
+ $('.oauth-login', this.container).each((i, element) => {
+ const href = $(element).attr('href');
+
+ if (rememberMe) {
+ $(element).attr('href', `${href}?remember_me=1`);
+ } else {
+ $(element).attr('href', href.replace('?remember_me=1', ''));
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/peek.js b/app/assets/javascripts/peek.js
deleted file mode 100644
index de1a99fa3bd..00000000000
--- a/app/assets/javascripts/peek.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'vendor/peek';
-import 'vendor/peek.performance_bar';
-
-$(document).on('click', '#peek-show-queries', (e) => {
- e.preventDefault();
- $('.peek-rblineprof-modal').hide();
- const $modal = $('#modal-peek-pg-queries');
- if ($modal.length) {
- $modal.modal('toggle');
- }
-});
-
-$(document).on('click', '.js-lineprof-file', (e) => {
- e.preventDefault();
- $(e.target).parents('.peek-rblineprof-file').find('.data').toggle();
-});
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
new file mode 100644
index 00000000000..9bbdf7f513c
--- /dev/null
+++ b/app/assets/javascripts/performance_bar.js
@@ -0,0 +1,62 @@
+import 'vendor/peek';
+import 'vendor/peek.performance_bar';
+
+export default class PerformanceBar {
+ constructor(opts) {
+ if (!PerformanceBar.singleton) {
+ this.init(opts);
+ PerformanceBar.singleton = this;
+ }
+ return PerformanceBar.singleton;
+ }
+
+ init(opts) {
+ const $container = $(opts.container);
+ this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
+ this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
+ this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
+ this.$lineProfileModal = $('#modal-peek-line-profile');
+ this.initEventListeners();
+ this.showModalOnLoad();
+ }
+
+ initEventListeners() {
+ this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
+ this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
+ $(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
+ }
+
+ showModalOnLoad() {
+ // When a lineprofiler query-string param is present, we show the line
+ // profiler modal upon page load
+ if (/lineprofiler/.test(window.location.search)) {
+ PerformanceBar.toggleModal(this.$lineProfileModal);
+ }
+ }
+
+ handleSQLProfileLink() {
+ PerformanceBar.toggleModal(this.$sqlProfileModal);
+ }
+
+ handleLineProfileLink(e) {
+ const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler');
+ const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
+ const shouldToggleModal = lineProfilerParameter.length > 0 &&
+ lineProfilerParameterRegex.test(e.currentTarget.href);
+
+ if (shouldToggleModal) {
+ e.preventDefault();
+ PerformanceBar.toggleModal(this.$lineProfileModal);
+ }
+ }
+
+ static toggleModal($modal) {
+ if ($modal.length) {
+ $modal.modal('toggle');
+ }
+ }
+
+ static toggleLineProfileFile(e) {
+ $(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
+ }
+}
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index b424e7f205d..50c725aa3d5 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -3,6 +3,7 @@ import Translate from '../vue_shared/translate';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
+import { setupPipelineVariableList } from './setup_pipeline_variable_list';
Vue.use(Translate);
@@ -39,4 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
gl.timezoneDropdown = new TimezoneDropdown();
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
+
+ setupPipelineVariableList($('.js-pipeline-variable-list'));
});
diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
new file mode 100644
index 00000000000..644efd10509
--- /dev/null
+++ b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
@@ -0,0 +1,71 @@
+function insertRow($row) {
+ const $rowClone = $row.clone();
+ $rowClone.removeAttr('data-is-persisted');
+ $rowClone.find('input, textarea').val('');
+ $row.after($rowClone);
+}
+
+function removeRow($row) {
+ const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
+
+ if (isPersisted) {
+ $row.hide();
+ $row
+ .find('.js-destroy-input')
+ .val(1);
+ } else {
+ $row.remove();
+ }
+}
+
+function checkIfRowTouched($row) {
+ return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
+}
+
+function setupPipelineVariableList(parent = document) {
+ const $parent = $(parent);
+
+ $parent.on('click', '.js-row-remove-button', (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+ removeRow($row);
+
+ e.preventDefault();
+ });
+
+ // Remove any empty rows except the last r
+ $parent.on('blur', '.js-user-input', (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+
+ const isTouched = checkIfRowTouched($row);
+ if ($row.is(':not(:last-child)') && !isTouched) {
+ removeRow($row);
+ }
+ });
+
+ // Always make sure there is an empty last row
+ $parent.on('input', '.js-user-input', () => {
+ const $lastRow = $parent.find('.js-row').last();
+
+ const isTouched = checkIfRowTouched($lastRow);
+ if (isTouched) {
+ insertRow($lastRow);
+ }
+ });
+
+ // Clear out the empty last row so it
+ // doesn't get submitted and throw validation errors
+ $parent.closest('form').on('submit', () => {
+ const $lastRow = $parent.find('.js-row').last();
+
+ const isTouched = checkIfRowTouched($lastRow);
+ if (!isTouched) {
+ $lastRow.find('input, textarea').attr('name', '');
+ }
+ });
+}
+
+export {
+ setupPipelineVariableList,
+ insertRow,
+ removeRow,
+};
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index c0f757269cb..fd89a1a85c3 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */
+import VisibilitySelect from './visibility_select';
+
function highlightChanges($elm) {
$elm.addClass('highlight-changes');
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
@@ -30,7 +32,7 @@ function highlightChanges($elm) {
ProjectNew.prototype.initVisibilitySelect = function() {
const visibilityContainer = document.querySelector('.js-visibility-select');
if (!visibilityContainer) return;
- const visibilitySelect = new gl.VisibilitySelect(visibilityContainer);
+ const visibilitySelect = new VisibilitySelect(visibilityContainer);
visibilitySelect.init();
const $visibilitySelect = $(visibilityContainer).find('select');
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 322162afdb8..d8f1fe10b26 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
import Cookies from 'js-cookie';
+import SidebarHeightManager from './sidebar_height_manager';
(function() {
this.Sidebar = (function() {
@@ -8,10 +9,6 @@ import Cookies from 'js-cookie';
this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside');
- this.$sidebarInner = this.sidebar.find('.issuable-sidebar');
- this.$navGitlab = $('.navbar-gitlab');
- this.$rightSidebar = $('.js-right-sidebar');
-
this.removeListeners();
this.addEventListeners();
}
@@ -25,16 +22,14 @@ import Cookies from 'js-cookie';
};
Sidebar.prototype.addEventListeners = function() {
+ SidebarHeightManager.init();
const $document = $(document);
- const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 20);
- const debouncedSetSidebarHeight = _.debounce(this.setSidebarHeight.bind(this), 200);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
- $(window).on('resize', () => throttledSetSidebarHeight());
- $document.on('scroll', () => debouncedSetSidebarHeight());
+
$document.on('click', '.js-sidebar-toggle', function(e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
@@ -212,18 +207,6 @@ import Cookies from 'js-cookie';
}
};
- Sidebar.prototype.setSidebarHeight = function() {
- const $navHeight = this.$navGitlab.outerHeight();
- const diff = $navHeight - $(window).scrollTop();
- if (diff > 0) {
- this.$rightSidebar.outerHeight($(window).height() - diff);
- this.$sidebarInner.height('100%');
- } else {
- this.$rightSidebar.outerHeight('100%');
- this.$sidebarInner.height('');
- }
- };
-
Sidebar.prototype.isOpen = function() {
return this.sidebar.is('.right-sidebar-expanded');
};
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index a4a7f3fa944..49d980212d6 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
if (Cookies.get(performanceBarCookieName) === 'true') {
Cookies.remove(performanceBarCookieName, { path: '/' });
} else {
- Cookies.set(performanceBarCookieName, true, { path: '/' });
+ Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
gl.utils.refreshCurrentPage();
};
diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js
new file mode 100644
index 00000000000..022415f22b2
--- /dev/null
+++ b/app/assets/javascripts/sidebar_height_manager.js
@@ -0,0 +1,33 @@
+export default {
+ init() {
+ if (!this.initialized) {
+ this.$window = $(window);
+ this.$rightSidebar = $('.js-right-sidebar');
+ this.$navHeight = $('.navbar-gitlab').outerHeight() +
+ $('.layout-nav').outerHeight() +
+ $('.sub-nav-scroll').outerHeight();
+
+ const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
+ const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
+
+ this.$window.on('scroll', throttledSetSidebarHeight);
+ this.$window.on('resize', debouncedSetSidebarHeight);
+ this.initialized = true;
+ }
+ },
+
+ setSidebarHeight() {
+ const currentScrollDepth = window.pageYOffset || 0;
+ const diff = this.$navHeight - currentScrollDepth;
+
+ if (diff > 0) {
+ const newSidebarHeight = window.innerHeight - diff;
+ this.$rightSidebar.outerHeight(newSidebarHeight);
+ this.sidebarHeightIsCustom = true;
+ } else if (this.sidebarHeightIsCustom) {
+ this.$rightSidebar.outerHeight('100%');
+ this.sidebarHeightIsCustom = false;
+ }
+ },
+};
+
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js
index 2587facc582..20255398047 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/signin_tabs_memoizer.js
@@ -2,56 +2,52 @@
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
-((global) => {
- /**
- * Memorize the last selected tab after reloading a page.
- * Does that setting the current selected tab in the localStorage
- */
- class ActiveTabMemoizer {
- constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
- this.currentTabKey = currentTabKey;
- this.tabSelector = tabSelector;
- this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
-
- this.bootstrap();
- }
-
- bootstrap() {
- const tabs = document.querySelectorAll(this.tabSelector);
- if (tabs.length > 0) {
- tabs[0].addEventListener('click', (e) => {
- if (e.target && e.target.nodeName === 'A') {
- const anchorName = e.target.getAttribute('href');
- this.saveData(anchorName);
- }
- });
- }
-
- this.showTab();
- }
+/**
+ * Memorize the last selected tab after reloading a page.
+ * Does that setting the current selected tab in the localStorage
+ */
+export default class SigninTabsMemoizer {
+ constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+ this.currentTabKey = currentTabKey;
+ this.tabSelector = tabSelector;
+ this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+
+ this.bootstrap();
+ }
- showTab() {
- const anchorName = this.readData();
- if (anchorName) {
- const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
- if (tab) {
- tab.click();
+ bootstrap() {
+ const tabs = document.querySelectorAll(this.tabSelector);
+ if (tabs.length > 0) {
+ tabs[0].addEventListener('click', (e) => {
+ if (e.target && e.target.nodeName === 'A') {
+ const anchorName = e.target.getAttribute('href');
+ this.saveData(anchorName);
}
- }
+ });
}
- saveData(val) {
- if (!this.isLocalStorageAvailable) return undefined;
+ this.showTab();
+ }
- return window.localStorage.setItem(this.currentTabKey, val);
+ showTab() {
+ const anchorName = this.readData();
+ if (anchorName) {
+ const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
+ if (tab) {
+ tab.click();
+ }
}
+ }
- readData() {
- if (!this.isLocalStorageAvailable) return null;
+ saveData(val) {
+ if (!this.isLocalStorageAvailable) return undefined;
- return window.localStorage.getItem(this.currentTabKey);
- }
+ return window.localStorage.setItem(this.currentTabKey, val);
}
- global.ActiveTabMemoizer = ActiveTabMemoizer;
-})(window);
+ readData() {
+ if (!this.isLocalStorageAvailable) return null;
+
+ return window.localStorage.getItem(this.currentTabKey);
+ }
+}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index c44892dae3d..4505a79a2df 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,96 +1,83 @@
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
-(function() {
- window.SingleFileDiff = (function() {
- var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+import FilesCommentButton from './files_comment_button';
- WRAPPER = '<div class="diff-content"></div>';
+const WRAPPER = '<div class="diff-content"></div>';
+const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
+const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
- LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
-
- ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+export default class SingleFileDiff {
+ constructor(file) {
+ this.file = file;
+ this.toggleDiff = this.toggleDiff.bind(this);
+ this.content = $('.diff-content', this.file);
+ this.$toggleIcon = $('.diff-toggle-caret', this.file);
+ this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
+ this.isOpen = !this.diffForPath;
+ if (this.diffForPath) {
+ this.collapsedContent = this.content;
+ this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+ this.content = null;
+ this.collapsedContent.after(this.loadingContent);
+ this.$toggleIcon.addClass('fa-caret-right');
+ } else {
+ this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+ this.content.after(this.collapsedContent);
+ this.$toggleIcon.addClass('fa-caret-down');
+ }
- COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
+ $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
+ this.toggleDiff($(e.target));
+ }).bind(this));
+ }
- function SingleFileDiff(file) {
- this.file = file;
- this.toggleDiff = this.toggleDiff.bind(this);
- this.content = $('.diff-content', this.file);
- this.$toggleIcon = $('.diff-toggle-caret', this.file);
- this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
- this.isOpen = !this.diffForPath;
- if (this.diffForPath) {
- this.collapsedContent = this.content;
- this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
- this.content = null;
- this.collapsedContent.after(this.loadingContent);
- this.$toggleIcon.addClass('fa-caret-right');
- } else {
- this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
- this.content.after(this.collapsedContent);
- this.$toggleIcon.addClass('fa-caret-down');
+ toggleDiff($target, cb) {
+ if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+ this.isOpen = !this.isOpen;
+ if (!this.isOpen && !this.hasError) {
+ this.content.hide();
+ this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
+ this.collapsedContent.show();
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ gl.diffNotesCompileComponents();
}
-
- $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
- this.toggleDiff($(e.target));
- }).bind(this));
+ } else if (this.content) {
+ this.collapsedContent.hide();
+ this.content.show();
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ gl.diffNotesCompileComponents();
+ }
+ } else {
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ return this.getContentHTML(cb);
}
+ }
- SingleFileDiff.prototype.toggleDiff = function($target, cb) {
- if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
- this.isOpen = !this.isOpen;
- if (!this.isOpen && !this.hasError) {
- this.content.hide();
- this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
- this.collapsedContent.show();
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- gl.diffNotesCompileComponents();
+ getContentHTML(cb) {
+ this.collapsedContent.hide();
+ this.loadingContent.show();
+ $.get(this.diffForPath, (function(_this) {
+ return function(data) {
+ _this.loadingContent.hide();
+ if (data.html) {
+ _this.content = $(data.html);
+ _this.content.syntaxHighlight();
+ } else {
+ _this.hasError = true;
+ _this.content = $(ERROR_HTML);
}
- } else if (this.content) {
- this.collapsedContent.hide();
- this.content.show();
- this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ _this.collapsedContent.after(_this.content);
+
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
- } else {
- this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
- return this.getContentHTML(cb);
- }
- };
-
- SingleFileDiff.prototype.getContentHTML = function(cb) {
- this.collapsedContent.hide();
- this.loadingContent.show();
- $.get(this.diffForPath, (function(_this) {
- return function(data) {
- _this.loadingContent.hide();
- if (data.html) {
- _this.content = $(data.html);
- _this.content.syntaxHighlight();
- } else {
- _this.hasError = true;
- _this.content = $(ERROR_HTML);
- }
- _this.collapsedContent.after(_this.content);
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- gl.diffNotesCompileComponents();
- }
+ FilesCommentButton.init($(_this.file));
- if (cb) cb();
- };
- })(this));
- };
-
- return SingleFileDiff;
- })();
-
- $.fn.singleFileDiff = function() {
- return this.each(function() {
- if (!$.data(this, 'singleFileDiff')) {
- return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
- }
- });
- };
-}).call(window);
+ if (cb) cb();
+ };
+ })(this));
+ }
+}
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index d1bdc353be2..2bf7a3a5d61 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -1,158 +1,157 @@
-/*
-* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
-* and controllable by a public API.
-*
-* */
-
-(() => {
- class SmartInterval {
- /**
- * @param { function } opts.callback Function to be called on each iteration (required)
- * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
- * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
- * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
- * when the page is hidden
- * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
- * @param { boolean } opts.lazyStart Configure if timer is initialized on
- * instantiation or lazily
- * @param { boolean } opts.immediateExecution Configure if callback should
- * be executed before the first interval.
- */
- constructor(opts = {}) {
- this.cfg = {
- callback: opts.callback,
- startingInterval: opts.startingInterval,
- maxInterval: opts.maxInterval,
- hiddenInterval: opts.hiddenInterval,
- incrementByFactorOf: opts.incrementByFactorOf,
- lazyStart: opts.lazyStart,
- immediateExecution: opts.immediateExecution,
- };
-
- this.state = {
- intervalId: null,
- currentInterval: this.cfg.startingInterval,
- pageVisibility: 'visible',
- };
-
- this.initInterval();
- }
- /* public */
-
- start() {
- const cfg = this.cfg;
- const state = this.state;
-
- if (cfg.immediateExecution) {
- cfg.immediateExecution = false;
- cfg.callback();
- }
+/**
+ * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
+ * and controllable by a public API.
+ */
+
+class SmartInterval {
+ /**
+ * @param { function } opts.callback Function to be called on each iteration (required)
+ * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
+ * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
+ * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
+ * when the page is hidden
+ * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
+ * @param { boolean } opts.lazyStart Configure if timer is initialized on
+ * instantiation or lazily
+ * @param { boolean } opts.immediateExecution Configure if callback should
+ * be executed before the first interval.
+ */
+ constructor(opts = {}) {
+ this.cfg = {
+ callback: opts.callback,
+ startingInterval: opts.startingInterval,
+ maxInterval: opts.maxInterval,
+ hiddenInterval: opts.hiddenInterval,
+ incrementByFactorOf: opts.incrementByFactorOf,
+ lazyStart: opts.lazyStart,
+ immediateExecution: opts.immediateExecution,
+ };
+
+ this.state = {
+ intervalId: null,
+ currentInterval: this.cfg.startingInterval,
+ pageVisibility: 'visible',
+ };
+
+ this.initInterval();
+ }
- state.intervalId = window.setInterval(() => {
- cfg.callback();
+ /* public */
- if (this.getCurrentInterval() === cfg.maxInterval) {
- return;
- }
+ start() {
+ const cfg = this.cfg;
+ const state = this.state;
- this.incrementInterval();
- this.resume();
- }, this.getCurrentInterval());
+ if (cfg.immediateExecution) {
+ cfg.immediateExecution = false;
+ cfg.callback();
}
- // cancel the existing timer, setting the currentInterval back to startingInterval
- cancel() {
- this.setCurrentInterval(this.cfg.startingInterval);
- this.stopTimer();
- }
+ state.intervalId = window.setInterval(() => {
+ cfg.callback();
- onVisibilityHidden() {
- if (this.cfg.hiddenInterval) {
- this.setCurrentInterval(this.cfg.hiddenInterval);
- this.resume();
- } else {
- this.cancel();
+ if (this.getCurrentInterval() === cfg.maxInterval) {
+ return;
}
- }
- // start a timer, using the existing interval
- resume() {
- this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
- this.start();
- }
+ this.incrementInterval();
+ this.resume();
+ }, this.getCurrentInterval());
+ }
- onVisibilityVisible() {
- this.cancel();
- this.start();
- }
+ // cancel the existing timer, setting the currentInterval back to startingInterval
+ cancel() {
+ this.setCurrentInterval(this.cfg.startingInterval);
+ this.stopTimer();
+ }
- destroy() {
+ onVisibilityHidden() {
+ if (this.cfg.hiddenInterval) {
+ this.setCurrentInterval(this.cfg.hiddenInterval);
+ this.resume();
+ } else {
this.cancel();
- document.removeEventListener('visibilitychange', this.handleVisibilityChange);
- $(document).off('visibilitychange').off('beforeunload');
}
+ }
- /* private */
+ // start a timer, using the existing interval
+ resume() {
+ this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
+ this.start();
+ }
- initInterval() {
- const cfg = this.cfg;
+ onVisibilityVisible() {
+ this.cancel();
+ this.start();
+ }
- if (!cfg.lazyStart) {
- this.start();
- }
+ destroy() {
+ this.cancel();
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
+ $(document).off('visibilitychange').off('beforeunload');
+ }
- this.initVisibilityChangeHandling();
- this.initPageUnloadHandling();
- }
+ /* private */
- initVisibilityChangeHandling() {
- // cancel interval when tab no longer shown (prevents cached pages from polling)
- document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
- }
+ initInterval() {
+ const cfg = this.cfg;
- initPageUnloadHandling() {
- // TODO: Consider refactoring in light of turbolinks removal.
- // prevent interval continuing after page change, when kept in cache by Turbolinks
- $(document).on('beforeunload', () => this.cancel());
+ if (!cfg.lazyStart) {
+ this.start();
}
- handleVisibilityChange(e) {
- this.state.pageVisibility = e.target.visibilityState;
- const intervalAction = this.isPageVisible() ?
- this.onVisibilityVisible :
- this.onVisibilityHidden;
+ this.initVisibilityChangeHandling();
+ this.initPageUnloadHandling();
+ }
- intervalAction.apply(this);
- }
+ initVisibilityChangeHandling() {
+ // cancel interval when tab no longer shown (prevents cached pages from polling)
+ document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
+ }
- getCurrentInterval() {
- return this.state.currentInterval;
- }
+ initPageUnloadHandling() {
+ // TODO: Consider refactoring in light of turbolinks removal.
+ // prevent interval continuing after page change, when kept in cache by Turbolinks
+ $(document).on('beforeunload', () => this.cancel());
+ }
- setCurrentInterval(newInterval) {
- this.state.currentInterval = newInterval;
- }
+ handleVisibilityChange(e) {
+ this.state.pageVisibility = e.target.visibilityState;
+ const intervalAction = this.isPageVisible() ?
+ this.onVisibilityVisible :
+ this.onVisibilityHidden;
- incrementInterval() {
- const cfg = this.cfg;
- const currentInterval = this.getCurrentInterval();
- if (cfg.hiddenInterval && !this.isPageVisible()) return;
- let nextInterval = currentInterval * cfg.incrementByFactorOf;
+ intervalAction.apply(this);
+ }
- if (nextInterval > cfg.maxInterval) {
- nextInterval = cfg.maxInterval;
- }
+ getCurrentInterval() {
+ return this.state.currentInterval;
+ }
+
+ setCurrentInterval(newInterval) {
+ this.state.currentInterval = newInterval;
+ }
+
+ incrementInterval() {
+ const cfg = this.cfg;
+ const currentInterval = this.getCurrentInterval();
+ if (cfg.hiddenInterval && !this.isPageVisible()) return;
+ let nextInterval = currentInterval * cfg.incrementByFactorOf;
- this.setCurrentInterval(nextInterval);
+ if (nextInterval > cfg.maxInterval) {
+ nextInterval = cfg.maxInterval;
}
- isPageVisible() { return this.state.pageVisibility === 'visible'; }
+ this.setCurrentInterval(nextInterval);
+ }
- stopTimer() {
- const state = this.state;
+ isPageVisible() { return this.state.pageVisibility === 'visible'; }
- state.intervalId = window.clearInterval(state.intervalId);
- }
+ stopTimer() {
+ const state = this.state;
+
+ state.intervalId = window.clearInterval(state.intervalId);
}
- gl.SmartInterval = SmartInterval;
-})(window.gl || (window.gl = {}));
+}
+
+window.gl.SmartInterval = SmartInterval;
diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js
index 2128007113f..3b6d999b1c3 100644
--- a/app/assets/javascripts/snippets_list.js
+++ b/app/assets/javascripts/snippets_list.js
@@ -1,13 +1,9 @@
-/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
+function SnippetsList() {
+ const $holder = $('.snippets-list-holder');
-(global => {
- global.gl = global.gl || {};
+ $holder.find('.pagination').on('ajax:success', (e, data) => {
+ $holder.replaceWith(data.html);
+ });
+}
- gl.SnippetsList = function() {
- var $holder = $('.snippets-list-holder');
-
- $holder.find('.pagination').on('ajax:success', (e, data) => {
- $holder.replaceWith(data.html);
- });
- };
-})(window);
+window.gl.SnippetsList = SnippetsList;
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index c75b44cc2fd..6d38124f1c1 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,30 +1,26 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
-(function() {
- this.Star = (function() {
- function Star() {
- $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
- var $starIcon, $starSpan, $this, toggleStar;
- $this = $(this);
- $starSpan = $this.find('span');
- $starIcon = $this.find('i');
- toggleStar = function(isStarred) {
- $this.parent().find('.star-count').text(data.star_count);
- if (isStarred) {
- $starSpan.removeClass('starred').text('Star');
- $starIcon.removeClass('fa-star').addClass('fa-star-o');
- } else {
- $starSpan.addClass('starred').text('Unstar');
- $starIcon.removeClass('fa-star-o').addClass('fa-star');
- }
- };
- toggleStar($starSpan.hasClass('starred'));
- }).on('ajax:error', function(e, xhr, status, error) {
- new Flash('Star toggle failed. Try again later.', 'alert');
- });
- }
-
- return Star;
- })();
-}).call(window);
+export default class Star {
+ constructor() {
+ $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
+ var $starIcon, $starSpan, $this, toggleStar;
+ $this = $(this);
+ $starSpan = $this.find('span');
+ $starIcon = $this.find('i');
+ toggleStar = function(isStarred) {
+ $this.parent().find('.star-count').text(data.star_count);
+ if (isStarred) {
+ $starSpan.removeClass('starred').text('Star');
+ $starIcon.removeClass('fa-star').addClass('fa-star-o');
+ } else {
+ $starSpan.addClass('starred').text('Unstar');
+ $starIcon.removeClass('fa-star-o').addClass('fa-star');
+ }
+ };
+ toggleStar($starSpan.hasClass('starred'));
+ }).on('ajax:error', function(e, xhr, status, error) {
+ new Flash('Star toggle failed. Try again later.', 'alert');
+ });
+ }
+}
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
index 5f9a3e00c22..bb4d68fcd49 100644
--- a/app/assets/javascripts/subscription.js
+++ b/app/assets/javascripts/subscription.js
@@ -1,47 +1,45 @@
-(() => {
- class Subscription {
- constructor(containerElm) {
- this.containerElm = containerElm;
+class Subscription {
+ constructor(containerElm) {
+ this.containerElm = containerElm;
- const subscribeButton = containerElm.querySelector('.js-subscribe-button');
- if (subscribeButton) {
- // remove class so we don't bind twice
- subscribeButton.classList.remove('js-subscribe-button');
- subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
- }
+ const subscribeButton = containerElm.querySelector('.js-subscribe-button');
+ if (subscribeButton) {
+ // remove class so we don't bind twice
+ subscribeButton.classList.remove('js-subscribe-button');
+ subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
+ }
- toggleSubscription(event) {
- const button = event.currentTarget;
- const buttonSpan = button.querySelector('span');
- if (!buttonSpan || button.classList.contains('disabled')) {
- return;
- }
- button.classList.add('disabled');
+ toggleSubscription(event) {
+ const button = event.currentTarget;
+ const buttonSpan = button.querySelector('span');
+ if (!buttonSpan || button.classList.contains('disabled')) {
+ return;
+ }
+ button.classList.add('disabled');
- const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
- const toggleActionUrl = this.containerElm.dataset.url;
+ const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
+ const toggleActionUrl = this.containerElm.dataset.url;
- $.post(toggleActionUrl, () => {
- button.classList.remove('disabled');
+ $.post(toggleActionUrl, () => {
+ button.classList.remove('disabled');
- // hack to allow this to work with the issue boards Vue object
- if (document.querySelector('html').classList.contains('issue-boards-page')) {
- gl.issueBoards.boardStoreIssueSet(
- 'subscribed',
- !gl.issueBoards.BoardsStore.detail.issue.subscribed,
- );
- } else {
- buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
- }
- });
- }
+ // hack to allow this to work with the issue boards Vue object
+ if (document.querySelector('html').classList.contains('issue-boards-page')) {
+ gl.issueBoards.boardStoreIssueSet(
+ 'subscribed',
+ !gl.issueBoards.BoardsStore.detail.issue.subscribed,
+ );
+ } else {
+ buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
+ }
+ });
+ }
- static bindAll(selector) {
- [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
- }
+ static bindAll(selector) {
+ [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
+}
- window.gl = window.gl || {};
- window.gl.Subscription = Subscription;
-})();
+window.gl = window.gl || {};
+window.gl.Subscription = Subscription;
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 0cd591c7320..37e39ce5477 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,34 +1,33 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
-(function() {
- this.SubscriptionSelect = (function() {
- function SubscriptionSelect() {
- $('.js-subscription-event').each(function(i, el) {
- var fieldName;
- fieldName = $(el).data("field-name");
- return $(el).glDropdown({
- selectable: true,
- fieldName: fieldName,
- toggleLabel: (function(_this) {
- return function(selected, el, instance) {
- var $item, label;
- label = 'Subscription';
- $item = instance.dropdown.find('.is-active');
- if ($item.length) {
- label = $item.text();
- }
- return label;
- };
- })(this),
- clicked: function(options) {
- return options.e.preventDefault();
- },
- id: function(obj, el) {
- return $(el).data("id");
- }
- });
+
+class SubscriptionSelect {
+ constructor() {
+ $('.js-subscription-event').each(function(i, el) {
+ var fieldName;
+ fieldName = $(el).data("field-name");
+ return $(el).glDropdown({
+ selectable: true,
+ fieldName: fieldName,
+ toggleLabel: (function(_this) {
+ return function(selected, el, instance) {
+ var $item, label;
+ label = 'Subscription';
+ $item = instance.dropdown.find('.is-active');
+ if ($item.length) {
+ label = $item.text();
+ }
+ return label;
+ };
+ })(this),
+ clicked: function(options) {
+ return options.e.preventDefault();
+ },
+ id: function(obj, el) {
+ return $(el).data("id");
+ }
});
- }
+ });
+ }
+}
- return SubscriptionSelect;
- })();
-}).call(window);
+window.SubscriptionSelect = SubscriptionSelect;
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 7c063fae045..662d6b36c16 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -9,19 +9,18 @@
//
// <div class="js-syntax-highlight"></div>
//
-(function() {
- $.fn.syntaxHighlight = function() {
- var $children;
- if ($(this).hasClass('js-syntax-highlight')) {
- // Given the element itself, apply highlighting
- return $(this).addClass(gon.user_color_scheme);
- } else {
- // Given a parent element, recurse to any of its applicable children
- $children = $(this).find('.js-syntax-highlight');
- if ($children.length) {
- return $children.syntaxHighlight();
- }
+$.fn.syntaxHighlight = function() {
+ var $children;
+
+ if ($(this).hasClass('js-syntax-highlight')) {
+ // Given the element itself, apply highlighting
+ return $(this).addClass(gon.user_color_scheme);
+ } else {
+ // Given a parent element, recurse to any of its applicable children
+ $children = $(this).find('.js-syntax-highlight');
+ if ($children.length) {
+ return $children.syntaxHighlight();
}
- };
-}).call(window);
+ }
+};
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 419c458ff34..c39f569da5e 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -2,7 +2,7 @@
import 'deckar01-task_list';
-class TaskList {
+export default class TaskList {
constructor(options = {}) {
this.selector = options.selector;
this.dataType = options.dataType;
@@ -48,6 +48,3 @@ class TaskList {
});
}
}
-
-window.gl = window.gl || {};
-window.gl.TaskList = TaskList;
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 7230946b484..cd305631c10 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -2,7 +2,7 @@
import UsersSelect from './users_select';
-class Todos {
+export default class Todos {
constructor() {
this.initFilters();
this.bindEvents();
@@ -159,6 +159,3 @@ class Todos {
}
}
}
-
-window.gl = window.gl || {};
-gl.Todos = Todos;
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 76a821c7a17..7777ed1c3dc 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,68 +1,64 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
-(function() {
- this.TreeView = (function() {
- function TreeView() {
- this.initKeyNav();
- // Code browser tree slider
- // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $(".tree-content-holder .tree-item").on('click', function(e) {
- var $clickedEl, path;
- $clickedEl = $(e.target);
- path = $('.tree-item-file-name a', this).attr('href');
- if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
- if (e.metaKey || e.which === 2) {
- e.preventDefault();
- return window.open(path, '_blank');
- } else {
- return gl.utils.visitUrl(path);
- }
+export default class TreeView {
+ constructor() {
+ this.initKeyNav();
+ // Code browser tree slider
+ // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
+ $(".tree-content-holder .tree-item").on('click', function(e) {
+ var $clickedEl, path;
+ $clickedEl = $(e.target);
+ path = $('.tree-item-file-name a', this).attr('href');
+ if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
+ if (e.metaKey || e.which === 2) {
+ e.preventDefault();
+ return window.open(path, '_blank');
+ } else {
+ return gl.utils.visitUrl(path);
}
- });
- // Show the "Loading commit data" for only the first element
- $('span.log_loading:first').removeClass('hide');
- }
+ }
+ });
+ // Show the "Loading commit data" for only the first element
+ $('span.log_loading:first').removeClass('hide');
+ }
- TreeView.prototype.initKeyNav = function() {
- var li, liSelected;
- li = $("tr.tree-item");
- liSelected = null;
- return $('body').keydown(function(e) {
- var next, path;
- if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
- return false;
- }
- if (e.which === 40) {
- if (liSelected) {
- next = liSelected.next();
- if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
- }
- } else {
- liSelected = li.eq(0).addClass("selected");
- }
- return $(liSelected).focus();
- } else if (e.which === 38) {
- if (liSelected) {
- next = liSelected.prev();
- if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
- }
- } else {
- liSelected = li.last().addClass("selected");
+ initKeyNav() {
+ var li, liSelected;
+ li = $("tr.tree-item");
+ liSelected = null;
+ return $('body').keydown(function(e) {
+ var next, path;
+ if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+ return false;
+ }
+ if (e.which === 40) {
+ if (liSelected) {
+ next = liSelected.next();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
}
- return $(liSelected).focus();
- } else if (e.which === 13) {
- path = $('.tree-item.selected .tree-item-file-name a').attr('href');
- if (path) {
- return gl.utils.visitUrl(path);
+ } else {
+ liSelected = li.eq(0).addClass("selected");
+ }
+ return $(liSelected).focus();
+ } else if (e.which === 38) {
+ if (liSelected) {
+ next = liSelected.prev();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
}
+ } else {
+ liSelected = li.last().addClass("selected");
}
- });
- };
-
- return TreeView;
- })();
-}).call(window);
+ return $(liSelected).focus();
+ } else if (e.which === 13) {
+ path = $('.tree-item.selected .tree-item-file-name a').attr('href');
+ if (path) {
+ return gl.utils.visitUrl(path);
+ }
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/usage_ping.js
index fd3af7d7ab6..2389056bd02 100644
--- a/app/assets/javascripts/usage_ping.js
+++ b/app/assets/javascripts/usage_ping.js
@@ -1,4 +1,4 @@
-function UsagePing() {
+export default function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint');
$.ajax({
@@ -10,6 +10,3 @@ function UsagePing() {
},
});
}
-
-window.gl = window.gl || {};
-window.gl.UsagePing = UsagePing;
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
index 19c9efe7fbd..9ef94ac7616 100644
--- a/app/assets/javascripts/user.js
+++ b/app/assets/javascripts/user.js
@@ -1,35 +1,37 @@
/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
import Cookies from 'js-cookie';
+import UserTabs from './user_tabs';
-((global) => {
- global.User = class {
- constructor({ action }) {
- this.action = action;
- this.placeProfileAvatarsToTop();
- this.initTabs();
- this.hideProjectLimitMessage();
- }
+class User {
+ constructor({ action }) {
+ this.action = action;
+ this.placeProfileAvatarsToTop();
+ this.initTabs();
+ this.hideProjectLimitMessage();
+ }
- placeProfileAvatarsToTop() {
- $('.profile-groups-avatars').tooltip({
- placement: 'top'
- });
- }
+ placeProfileAvatarsToTop() {
+ $('.profile-groups-avatars').tooltip({
+ placement: 'top'
+ });
+ }
- initTabs() {
- return new global.UserTabs({
- parentEl: '.user-profile',
- action: this.action
- });
- }
+ initTabs() {
+ return new UserTabs({
+ parentEl: '.user-profile',
+ action: this.action
+ });
+ }
- hideProjectLimitMessage() {
- $('.hide-project-limit-message').on('click', e => {
- e.preventDefault();
- Cookies.set('hide_project_limit_message', 'false');
- $(this).parents('.project-limit-message').remove();
- });
- }
- };
-})(window.gl || (window.gl = {}));
+ hideProjectLimitMessage() {
+ $('.hide-project-limit-message').on('click', e => {
+ e.preventDefault();
+ Cookies.set('hide_project_limit_message', 'false');
+ $(this).parents('.project-limit-message').remove();
+ });
+ }
+}
+
+window.gl = window.gl || {};
+window.gl.User = User;
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
index ce7eb76dc71..f8e23c8624d 100644
--- a/app/assets/javascripts/user_tabs.js
+++ b/app/assets/javascripts/user_tabs.js
@@ -59,117 +59,115 @@ content on the Users#show page.
</div>
</div>
*/
-((global) => {
- class UserTabs {
- constructor ({ defaultAction, action, parentEl }) {
- this.loaded = {};
- this.defaultAction = defaultAction || 'activity';
- this.action = action || this.defaultAction;
- this.$parentEl = $(parentEl) || $(document);
- this._location = window.location;
- this.$parentEl.find('.nav-links a')
- .each((i, navLink) => {
- this.loaded[$(navLink).attr('data-action')] = false;
- });
- this.actions = Object.keys(this.loaded);
- this.bindEvents();
-
- if (this.action === 'show') {
- this.action = this.defaultAction;
- }
- this.activateTab(this.action);
+export default class UserTabs {
+ constructor ({ defaultAction, action, parentEl }) {
+ this.loaded = {};
+ this.defaultAction = defaultAction || 'activity';
+ this.action = action || this.defaultAction;
+ this.$parentEl = $(parentEl) || $(document);
+ this._location = window.location;
+ this.$parentEl.find('.nav-links a')
+ .each((i, navLink) => {
+ this.loaded[$(navLink).attr('data-action')] = false;
+ });
+ this.actions = Object.keys(this.loaded);
+ this.bindEvents();
+
+ if (this.action === 'show') {
+ this.action = this.defaultAction;
}
- bindEvents() {
- this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
+ this.activateTab(this.action);
+ }
- this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
- .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
+ bindEvents() {
+ this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
- this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
- }
+ this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
+ .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
- changeProjectsPage(e) {
- e.preventDefault();
+ this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
+ }
- $('.tab-pane.active').empty();
- const endpoint = $(e.target).attr('href');
- this.loadTab(this.getCurrentAction(), endpoint);
- }
+ changeProjectsPage(e) {
+ e.preventDefault();
- tabShown(event) {
- const $target = $(event.target);
- const action = $target.data('action');
- const source = $target.attr('href');
- const endpoint = $target.data('endpoint');
- this.setTab(action, endpoint);
- return this.setCurrentAction(source);
- }
+ $('.tab-pane.active').empty();
+ const endpoint = $(e.target).attr('href');
+ this.loadTab(this.getCurrentAction(), endpoint);
+ }
- activateTab(action) {
- return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
- .tab('show');
- }
+ tabShown(event) {
+ const $target = $(event.target);
+ const action = $target.data('action');
+ const source = $target.attr('href');
+ const endpoint = $target.data('endpoint');
+ this.setTab(action, endpoint);
+ return this.setCurrentAction(source);
+ }
- setTab(action, endpoint) {
- if (this.loaded[action]) {
- return;
- }
- if (action === 'activity') {
- this.loadActivities();
- }
+ activateTab(action) {
+ return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
+ .tab('show');
+ }
- const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
- if (loadableActions.indexOf(action) > -1) {
- return this.loadTab(action, endpoint);
- }
+ setTab(action, endpoint) {
+ if (this.loaded[action]) {
+ return;
+ }
+ if (action === 'activity') {
+ this.loadActivities();
}
- loadTab(action, endpoint) {
- return $.ajax({
- beforeSend: () => this.toggleLoading(true),
- complete: () => this.toggleLoading(false),
- dataType: 'json',
- type: 'GET',
- url: endpoint,
- success: (data) => {
- const tabSelector = `div#${action}`;
- this.$parentEl.find(tabSelector).html(data.html);
- this.loaded[action] = true;
- return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
- }
- });
+ const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
+ if (loadableActions.indexOf(action) > -1) {
+ return this.loadTab(action, endpoint);
}
+ }
- loadActivities() {
- if (this.loaded['activity']) {
- return;
+ loadTab(action, endpoint) {
+ return $.ajax({
+ beforeSend: () => this.toggleLoading(true),
+ complete: () => this.toggleLoading(false),
+ dataType: 'json',
+ type: 'GET',
+ url: endpoint,
+ success: (data) => {
+ const tabSelector = `div#${action}`;
+ this.$parentEl.find(tabSelector).html(data.html);
+ this.loaded[action] = true;
+ return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
}
- const $calendarWrap = this.$parentEl.find('.user-calendar');
- $calendarWrap.load($calendarWrap.data('href'));
- new gl.Activities();
- return this.loaded['activity'] = true;
- }
+ });
+ }
- toggleLoading(status) {
- return this.$parentEl.find('.loading-status .loading')
- .toggle(status);
+ loadActivities() {
+ if (this.loaded['activity']) {
+ return;
}
+ const $calendarWrap = this.$parentEl.find('.user-calendar');
+ $calendarWrap.load($calendarWrap.data('href'));
+ new gl.Activities();
+ return this.loaded['activity'] = true;
+ }
- setCurrentAction(source) {
- let new_state = source;
- new_state = new_state.replace(/\/+$/, '');
- new_state += this._location.search + this._location.hash;
- history.replaceState({
- url: new_state
- }, document.title, new_state);
- return new_state;
- }
+ toggleLoading(status) {
+ return this.$parentEl.find('.loading-status .loading')
+ .toggle(status);
+ }
- getCurrentAction() {
- return this.$parentEl.find('.nav-links .active a').data('action');
- }
+ setCurrentAction(source) {
+ let new_state = source;
+ new_state = new_state.replace(/\/+$/, '');
+ new_state += this._location.search + this._location.hash;
+ history.replaceState({
+ url: new_state
+ }, document.title, new_state);
+ return new_state;
+ }
+
+ getCurrentAction() {
+ return this.$parentEl.find('.nav-links .active a').data('action');
}
- global.UserTabs = UserTabs;
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js
index 137cefa3b8e..a348d69153c 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/username_validator.js
@@ -1,135 +1,131 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
-((global) => {
- const debounceTimeoutDuration = 1000;
- const invalidInputClass = 'gl-field-error-outline';
- const successInputClass = 'gl-field-success-outline';
- const unavailableMessageSelector = '.username .validation-error';
- const successMessageSelector = '.username .validation-success';
- const pendingMessageSelector = '.username .validation-pending';
- const invalidMessageSelector = '.username .gl-field-error';
-
- class UsernameValidator {
- constructor() {
- this.inputElement = $('#new_user_username');
- this.inputDomElement = this.inputElement.get(0);
- this.state = {
- available: false,
- valid: false,
- pending: false,
- empty: true
- };
-
- const debounceTimeout = _.debounce((username) => {
- this.validateUsername(username);
- }, debounceTimeoutDuration);
-
- this.inputElement.on('keyup.username_check', () => {
- const username = this.inputElement.val();
-
- this.state.valid = this.inputDomElement.validity.valid;
- this.state.empty = !username.length;
-
- if (this.state.valid) {
- return debounceTimeout(username);
- }
-
- this.renderState();
- });
-
- // Override generic field validation
- this.inputElement.on('invalid', this.interceptInvalid.bind(this));
- }
-
- renderState() {
- // Clear all state
- this.clearFieldValidationState();
+const debounceTimeoutDuration = 1000;
+const invalidInputClass = 'gl-field-error-outline';
+const successInputClass = 'gl-field-success-outline';
+const unavailableMessageSelector = '.username .validation-error';
+const successMessageSelector = '.username .validation-success';
+const pendingMessageSelector = '.username .validation-pending';
+const invalidMessageSelector = '.username .gl-field-error';
+
+export default class UsernameValidator {
+ constructor() {
+ this.inputElement = $('#new_user_username');
+ this.inputDomElement = this.inputElement.get(0);
+ this.state = {
+ available: false,
+ valid: false,
+ pending: false,
+ empty: true
+ };
+
+ const debounceTimeout = _.debounce((username) => {
+ this.validateUsername(username);
+ }, debounceTimeoutDuration);
+
+ this.inputElement.on('keyup.username_check', () => {
+ const username = this.inputElement.val();
+
+ this.state.valid = this.inputDomElement.validity.valid;
+ this.state.empty = !username.length;
- if (this.state.valid && this.state.available) {
- return this.setSuccessState();
+ if (this.state.valid) {
+ return debounceTimeout(username);
}
- if (this.state.empty) {
- return this.clearFieldValidationState();
- }
+ this.renderState();
+ });
- if (this.state.pending) {
- return this.setPendingState();
- }
+ // Override generic field validation
+ this.inputElement.on('invalid', this.interceptInvalid.bind(this));
+ }
- if (!this.state.available) {
- return this.setUnavailableState();
- }
+ renderState() {
+ // Clear all state
+ this.clearFieldValidationState();
- if (!this.state.valid) {
- return this.setInvalidState();
- }
+ if (this.state.valid && this.state.available) {
+ return this.setSuccessState();
}
- interceptInvalid(event) {
- event.preventDefault();
- event.stopPropagation();
+ if (this.state.empty) {
+ return this.clearFieldValidationState();
}
- validateUsername(username) {
- if (this.state.valid) {
- this.state.pending = true;
- this.state.available = false;
- this.renderState();
- return $.ajax({
- type: 'GET',
- url: `${gon.relative_url_root}/users/${username}/exists`,
- dataType: 'json',
- success: (res) => this.setAvailabilityState(res.exists)
- });
- }
+ if (this.state.pending) {
+ return this.setPendingState();
}
- setAvailabilityState(usernameTaken) {
- if (usernameTaken) {
- this.state.valid = false;
- this.state.available = false;
- } else {
- this.state.available = true;
- }
- this.state.pending = false;
- this.renderState();
+ if (!this.state.available) {
+ return this.setUnavailableState();
}
- clearFieldValidationState() {
- this.inputElement.siblings('p').hide();
-
- this.inputElement.removeClass(invalidInputClass)
- .removeClass(successInputClass);
+ if (!this.state.valid) {
+ return this.setInvalidState();
}
+ }
- setUnavailableState() {
- const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
- this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
- $usernameUnavailableMessage.show();
- }
+ interceptInvalid(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
- setSuccessState() {
- const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
- this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
- $usernameSuccessMessage.show();
+ validateUsername(username) {
+ if (this.state.valid) {
+ this.state.pending = true;
+ this.state.available = false;
+ this.renderState();
+ return $.ajax({
+ type: 'GET',
+ url: `${gon.relative_url_root}/users/${username}/exists`,
+ dataType: 'json',
+ success: (res) => this.setAvailabilityState(res.exists)
+ });
}
+ }
- setPendingState() {
- const $usernamePendingMessage = $(pendingMessageSelector);
- if (this.state.pending) {
- $usernamePendingMessage.show();
- } else {
- $usernamePendingMessage.hide();
- }
+ setAvailabilityState(usernameTaken) {
+ if (usernameTaken) {
+ this.state.valid = false;
+ this.state.available = false;
+ } else {
+ this.state.available = true;
}
+ this.state.pending = false;
+ this.renderState();
+ }
- setInvalidState() {
- const $inputErrorMessage = $(invalidMessageSelector);
- this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
- $inputErrorMessage.show();
+ clearFieldValidationState() {
+ this.inputElement.siblings('p').hide();
+
+ this.inputElement.removeClass(invalidInputClass)
+ .removeClass(successInputClass);
+ }
+
+ setUnavailableState() {
+ const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $usernameUnavailableMessage.show();
+ }
+
+ setSuccessState() {
+ const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
+ this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
+ $usernameSuccessMessage.show();
+ }
+
+ setPendingState() {
+ const $usernamePendingMessage = $(pendingMessageSelector);
+ if (this.state.pending) {
+ $usernamePendingMessage.show();
+ } else {
+ $usernamePendingMessage.hide();
}
}
- global.UsernameValidator = UsernameValidator;
-})(window);
+ setInvalidState() {
+ const $inputErrorMessage = $(invalidMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $inputErrorMessage.show();
+ }
+}
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 46efdcf4202..5728afb4c59 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
- var isAuthorFilter;
- isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
diff --git a/app/assets/javascripts/version_check_image.js b/app/assets/javascripts/version_check_image.js
index 88ba991af47..ec515e892c6 100644
--- a/app/assets/javascripts/version_check_image.js
+++ b/app/assets/javascripts/version_check_image.js
@@ -3,6 +3,3 @@ export default class VersionCheckImage {
imageElement.off('error').on('error', () => imageElement.hide());
}
}
-
-window.gl = window.gl || {};
-gl.VersionCheckImage = VersionCheckImage;
diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js
index f712d7ba930..0c928d0d5f6 100644
--- a/app/assets/javascripts/visibility_select.js
+++ b/app/assets/javascripts/visibility_select.js
@@ -1,27 +1,21 @@
-(() => {
- const gl = window.gl || (window.gl = {});
-
- class VisibilitySelect {
- constructor(container) {
- if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
- this.container = container;
- this.helpBlock = this.container.querySelector('.help-block');
- this.select = this.container.querySelector('select');
- }
-
- init() {
- if (this.select) {
- this.updateHelpText();
- this.select.addEventListener('change', this.updateHelpText.bind(this));
- } else {
- this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
- }
- }
+export default class VisibilitySelect {
+ constructor(container) {
+ if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
+ this.container = container;
+ this.helpBlock = this.container.querySelector('.help-block');
+ this.select = this.container.querySelector('select');
+ }
- updateHelpText() {
- this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
+ init() {
+ if (this.select) {
+ this.updateHelpText();
+ this.select.addEventListener('change', this.updateHelpText.bind(this));
+ } else {
+ this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
}
- gl.VisibilitySelect = VisibilitySelect;
-})();
+ updateHelpText() {
+ this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
+ }
+}
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 e8b3cf2f729..c02e10128e2 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
@@ -17,9 +17,6 @@ export default {
return hasCI && !ciStatus;
},
- hasPipeline() {
- return Object.keys(this.mr.pipeline || {}).length > 0;
- },
svg() {
return statusIconEntityMap.icon_status_failed;
},
@@ -33,11 +30,7 @@ export default {
template: `
<div class="mr-widget-heading">
<div class="ci-widget">
- <template v-if="!hasPipeline">
- <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
- Waiting for pipeline...
- </template>
- <template v-else-if="hasCIError">
+ <template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link">
<span
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
index 41b1d0165b0..15581d5c2a0 100644
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue
@@ -12,9 +12,18 @@
required: false,
default: '1',
},
+
+ inline: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
+ rootElementType() {
+ return this.inline ? 'span' : 'div';
+ },
cssClass() {
return `fa-${this.size}x`;
},
@@ -22,12 +31,14 @@
};
</script>
<template>
- <div class="text-center">
+ <component
+ :is="this.rootElementType"
+ class="text-center">
<i
class="fa fa-spin fa-spinner"
:class="cssClass"
aria-hidden="true"
:aria-label="label">
</i>
- </div>
+ </component>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index e6977681e96..8303c556f64 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -64,6 +64,12 @@
*/
return new gl.GLForm($(this.$refs['gl-form']), true);
},
+ beforeDestroy() {
+ const glForm = $(this.$refs['gl-form']).data('gl-form');
+ if (glForm) {
+ glForm.destroy();
+ }
+ },
};
</script>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 5e7df22dd83..c9dbc048345 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -46,6 +46,8 @@ export default {
},
methods: {
changePage(e) {
+ if (e.target.parentElement.classList.contains('disabled')) return;
+
const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo;
@@ -82,7 +84,9 @@ export default {
const page = this.pageInfo.page;
const items = [];
- if (page > 1) items.push({ title: FIRST });
+ if (page > 1) {
+ items.push({ title: FIRST, first: true });
+ }
if (page > 1) {
items.push({ title: PREV, prev: true });
@@ -110,7 +114,9 @@ export default {
items.push({ title: NEXT, next: true });
}
- if (total - page >= 1) items.push({ title: LAST, last: true });
+ if (total - page >= 1) {
+ items.push({ title: LAST, last: true });
+ }
return items;
},
@@ -124,13 +130,15 @@ export default {
v-for="item in getItems"
:class="{
page: item.page,
- prev: item.prev,
- next: item.next,
+ 'js-previous-button': item.prev,
+ 'js-next-button': item.next,
+ 'js-last-button': item.last,
+ 'js-first-button': item.first,
separator: item.separator,
active: item.active,
disabled: item.disabled
}">
- <a @click="changePage($event)">{{item.title}}</a>
+ <a @click.prevent="changePage($event)">{{item.title}}</a>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js
new file mode 100644
index 00000000000..9a9cf395fb8
--- /dev/null
+++ b/app/assets/javascripts/webpack.js
@@ -0,0 +1,9 @@
+/**
+ * This is the first script loaded by webpack's runtime. It is used to manually configure
+ * config.output.publicPath to account for relative_url_root or CDN settings which cannot be
+ * baked-in to our webpack bundles.
+ */
+
+if (gon && gon.webpack_public_path) {
+ __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line
+}
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
index 4194c1bc08d..00676bcb0b3 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -1,69 +1,64 @@
-/* eslint-disable no-param-reassign */
/* global Breakpoints */
import 'vendor/jquery.nicescroll';
import './breakpoints';
-((global) => {
- class Wikis {
- constructor() {
- this.bp = Breakpoints.get();
- this.sidebarEl = document.querySelector('.js-wiki-sidebar');
- this.sidebarExpanded = false;
- $(this.sidebarEl).niceScroll();
+export default class Wikis {
+ constructor() {
+ this.bp = Breakpoints.get();
+ this.sidebarEl = document.querySelector('.js-wiki-sidebar');
+ this.sidebarExpanded = false;
+ $(this.sidebarEl).niceScroll();
- const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
- for (let i = 0; i < sidebarToggles.length; i += 1) {
- sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
- }
-
- this.newWikiForm = document.querySelector('form.new-wiki-page');
- if (this.newWikiForm) {
- this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
- }
+ const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
+ for (let i = 0; i < sidebarToggles.length; i += 1) {
+ sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
+ }
- window.addEventListener('resize', () => this.renderSidebar());
- this.renderSidebar();
+ this.newWikiForm = document.querySelector('form.new-wiki-page');
+ if (this.newWikiForm) {
+ this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
}
- handleNewWikiSubmit(e) {
- if (!this.newWikiForm) return;
+ window.addEventListener('resize', () => this.renderSidebar());
+ this.renderSidebar();
+ }
- const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
- const slug = gl.text.slugify(slugInput.value);
+ handleNewWikiSubmit(e) {
+ if (!this.newWikiForm) return;
- if (slug.length > 0) {
- const wikisPath = slugInput.getAttribute('data-wikis-path');
- window.location.href = `${wikisPath}/${slug}`;
- e.preventDefault();
- }
- }
+ const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
+ const slug = gl.text.slugify(slugInput.value);
- handleToggleSidebar(e) {
+ if (slug.length > 0) {
+ const wikisPath = slugInput.getAttribute('data-wikis-path');
+ window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
- this.sidebarExpanded = !this.sidebarExpanded;
- this.renderSidebar();
}
+ }
- sidebarCanCollapse() {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
- return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
- }
+ handleToggleSidebar(e) {
+ e.preventDefault();
+ this.sidebarExpanded = !this.sidebarExpanded;
+ this.renderSidebar();
+ }
- renderSidebar() {
- if (!this.sidebarEl) return;
- const { classList } = this.sidebarEl;
- if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
- if (!classList.contains('right-sidebar-expanded')) {
- classList.remove('right-sidebar-collapsed');
- classList.add('right-sidebar-expanded');
- }
- } else if (classList.contains('right-sidebar-expanded')) {
- classList.add('right-sidebar-collapsed');
- classList.remove('right-sidebar-expanded');
+ sidebarCanCollapse() {
+ const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+ }
+
+ renderSidebar() {
+ if (!this.sidebarEl) return;
+ const { classList } = this.sidebarEl;
+ if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+ if (!classList.contains('right-sidebar-expanded')) {
+ classList.remove('right-sidebar-collapsed');
+ classList.add('right-sidebar-expanded');
}
+ } else if (classList.contains('right-sidebar-expanded')) {
+ classList.add('right-sidebar-collapsed');
+ classList.remove('right-sidebar-expanded');
}
}
-
- global.Wikis = Wikis;
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index b7fe552dec2..99c7644e4d9 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */
/* global Mousetrap */
// Zen Mode (full screen) textarea
@@ -34,65 +34,62 @@ window.Dropzone = Dropzone;
// **Cancelable** No
// **Target** a.js-zen-leave
//
-(function() {
- this.ZenMode = (function() {
- function ZenMode() {
- this.active_backdrop = null;
- this.active_textarea = null;
- $(document).on('click', '.js-zen-enter', function(e) {
- e.preventDefault();
- return $(e.currentTarget).trigger('zen_mode:enter');
- });
- $(document).on('click', '.js-zen-leave', function(e) {
- e.preventDefault();
- return $(e.currentTarget).trigger('zen_mode:leave');
- });
- $(document).on('zen_mode:enter', (function(_this) {
- return function(e) {
- return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
- };
- })(this));
- $(document).on('zen_mode:leave', (function(_this) {
- return function(e) {
- return _this.exit();
- };
- })(this));
- $(document).on('keydown', function(e) {
- // Esc
- if (e.keyCode === 27) {
- e.preventDefault();
- return $(document).trigger('zen_mode:leave');
- }
- });
- }
-
- ZenMode.prototype.enter = function(backdrop) {
- Mousetrap.pause();
- this.active_backdrop = $(backdrop);
- this.active_backdrop.addClass('fullscreen');
- this.active_textarea = this.active_backdrop.find('textarea');
- // Prevent a user-resized textarea from persisting to fullscreen
- this.active_textarea.removeAttr('style');
- return this.active_textarea.focus();
- };
- ZenMode.prototype.exit = function() {
- if (this.active_textarea) {
- Mousetrap.unpause();
- this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
- this.scrollTo(this.active_textarea);
- this.active_textarea = null;
- this.active_backdrop = null;
- return Dropzone.forElement('.div-dropzone').enable();
+export default class ZenMode {
+ constructor() {
+ this.active_backdrop = null;
+ this.active_textarea = null;
+ $(document).on('click', '.js-zen-enter', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:enter');
+ });
+ $(document).on('click', '.js-zen-leave', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:leave');
+ });
+ $(document).on('zen_mode:enter', (function(_this) {
+ return function(e) {
+ return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
+ };
+ })(this));
+ $(document).on('zen_mode:leave', (function(_this) {
+ return function(e) {
+ return _this.exit();
+ };
+ })(this));
+ $(document).on('keydown', function(e) {
+ // Esc
+ if (e.keyCode === 27) {
+ e.preventDefault();
+ return $(document).trigger('zen_mode:leave');
}
- };
+ });
+ }
+
+ enter(backdrop) {
+ Mousetrap.pause();
+ this.active_backdrop = $(backdrop);
+ this.active_backdrop.addClass('fullscreen');
+ this.active_textarea = this.active_backdrop.find('textarea');
+ // Prevent a user-resized textarea from persisting to fullscreen
+ this.active_textarea.removeAttr('style');
+ return this.active_textarea.focus();
+ }
- ZenMode.prototype.scrollTo = function(zen_area) {
- return $.scrollTo(zen_area, 0, {
- offset: -150
- });
- };
+ exit() {
+ if (this.active_textarea) {
+ Mousetrap.unpause();
+ this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
+ this.scrollTo(this.active_textarea);
+ this.active_textarea = null;
+ this.active_backdrop = null;
+ return Dropzone.forElement('.div-dropzone').enable();
+ }
+ }
- return ZenMode;
- })();
-}).call(window);
+ scrollTo(zen_area) {
+ return $.scrollTo(zen_area, 0, {
+ offset: -150
+ });
+ }
+}
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index a2fa2e7769b..c0224d3bfa9 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -1,6 +1,29 @@
+.blank-state-parent-container {
+ display: flex;
+
+ .section-container {
+ display: flex;
+ flex: 1;
+ padding: 10px;
+ }
+
+ .section-body {
+ width: 100%;
+ height: 100%;
+ padding-bottom: 25px;
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+
+ &.section-ee-trial {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+}
+
.blank-state-welcome {
text-align: center;
- border-bottom: 1px solid $border-color;
.blank-state-text {
margin-bottom: 0;
@@ -10,6 +33,10 @@
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
+}
+
+.blank-state.ee-trial {
+ padding: 20px;
text-align: center;
}
@@ -20,20 +47,24 @@
.blank-state-icon {
padding-bottom: 20px;
- color: $gray-darkest;
font-size: 56px;
- path,
- polygon {
- fill: currentColor;
+ svg {
+ display: block;
+ margin: auto;
+ }
+}
+
+@media (min-width: $screen-sm-max) {
+ .section-welcome .blank-state-icon svg {
+ width: 130%;
}
}
.blank-state-title {
margin-top: 0;
- margin-bottom: 5px;
+ margin-bottom: 10px;
font-size: 18px;
- font-weight: normal;
}
.blank-state-text {
@@ -49,3 +80,24 @@
.blank-state-welcome-title {
font-size: 24px;
}
+
+@media (max-width: $screen-md-min) {
+ .blank-state-parent-container {
+ &,
+ .section-container {
+ display: block;
+ }
+ }
+
+ .blank-state {
+ text-align: center;
+ }
+
+ .blank-state-icon {
+ padding-bottom: 0;
+ }
+
+ .blank-state-body {
+ margin-top: 15px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 4369ae78bde..6eabdc63d9e 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -20,17 +20,29 @@
color: $text;
border-color: $border;
+ > .icon {
+ color: $text;
+ }
+
&:hover,
&:focus {
background-color: $hover-background;
border-color: $hover-border;
color: $hover-text;
+
+ > .icon {
+ color: $hover-text;
+ }
}
&:active {
background-color: $active-background;
border-color: $active-border;
color: $hover-text;
+
+ > .icon {
+ color: $hover-text;
+ }
}
}
@@ -163,7 +175,8 @@
@include btn-orange;
}
- &.btn-close {
+ &.btn-close,
+ &.btn-close-color {
@include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
}
@@ -181,7 +194,8 @@
float: right;
}
- &.btn-reopen {
+ &.btn-reopen,
+ .btn-reopen-color {
/* should be same as parent class for now */
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 4f54ca24940..dc4ed42544f 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -295,9 +295,74 @@
}
}
-.filtered-search-box-input-container .dropdown-menu,
-.filtered-search-box-input-container .dropdown-menu-nav,
-.comment-type-dropdown .dropdown-menu {
+.droplab-dropdown {
+ .description {
+ display: inline-block;
+ white-space: normal;
+ margin-left: 5px;
+ }
+
+ .dropdown-toggle > i {
+ pointer-events: none;
+ }
+
+ li {
+ padding: $gl-btn-padding $gl-btn-padding 2px;
+ cursor: pointer;
+
+ > a,
+ > button {
+ display: flex;
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
+ text-overflow: inherit;
+ background-color: inherit;
+ color: inherit;
+ border: inherit;
+ text-align: left;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
+
+ &.btn .fa:not(:last-child) {
+ margin-left: 5px;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ background-color: $dropdown-hover-color;
+ color: $white-light;
+ }
+
+ &.droplab-item-selected i {
+ visibility: visible;
+ }
+
+ .icon {
+ visibility: hidden;
+ }
+ }
+
+ .icon {
+ display: inline-block;
+ vertical-align: top;
+ padding-top: 2px;
+ }
+
+ .divider {
+ margin: 0 8px;
+ padding: 0;
+ border-top: $gray-darkest;
+ }
+}
+
+.droplab-dropdown .dropdown-menu,
+.droplab-dropdown .dropdown-menu-nav {
display: none;
opacity: 1;
visibility: visible;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 245117b2559..c7c2684d548 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -17,6 +17,8 @@
max-width: $limited-layout-width-sm;
margin-left: auto;
margin-right: auto;
+ padding-top: 64px;
+ padding-bottom: 64px;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 767cf5ffea5..f05348ee4e3 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -70,6 +70,13 @@
.input-token {
max-width: 200px;
+ padding: 0;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
}
.input-token:only-child,
@@ -156,6 +163,16 @@
}
}
+.droplab-dropdown li.filtered-search-token {
+ padding: 0;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
+}
+
.filtered-search-term {
.name {
background-color: inherit;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 38727e15c6f..e59cd0eea82 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -343,6 +343,12 @@ ul.indent-list {
.group-row {
padding: 0;
border: none;
+
+ &:last-of-type {
+ .group-row-contents:not(:hover) {
+ border-bottom: 1px solid transparent;
+ }
+ }
}
.group-row-contents {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 7098203321d..a28f54936be 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -21,3 +21,9 @@ body.modal-open {
width: 860px;
}
}
+
+@media (min-width: $screen-lg-min) {
+ .modal-full {
+ width: 98%;
+ }
+}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 5cf9330b8f8..542b641e3dd 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -92,7 +92,7 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
- padding: 10px 20px;
+ padding: 10px 0;
}
.issues-bulk-update.right-sidebar {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 630f557602c..3f032776d82 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -74,9 +74,17 @@ $red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
-$purple-700: #4a2192;
-$purple-800: #2c0a5c;
-$purple-900: #380d75;
+$indigo-50: #f7f7ff;
+$indigo-100: #ebebfa;
+$indigo-200: #d1d1f0;
+$indigo-300: #a6a6de;
+$indigo-400: #7c7ccc;
+$indigo-500: #6666c4;
+$indigo-600: #5b5bbd;
+$indigo-700: #4b4ba3;
+$indigo-800: #393982;
+$indigo-900: #292961;
+$indigo-950: #1a1a40;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
@@ -103,6 +111,7 @@ $well-light-text-color: #5b6169;
*/
$gl-font-size: 14px;
$gl-text-color: rgba(0, 0, 0, .85);
+$gl-text-color-light: rgba(0, 0, 0, .7);
$gl-text-color-secondary: rgba(0, 0, 0, .55);
$gl-text-color-disabled: rgba(0, 0, 0, .35);
$gl-text-color-inverted: rgba(255, 255, 255, 1.0);
@@ -150,6 +159,7 @@ $code_line_height: 1.6;
* Padding
*/
$gl-padding: 16px;
+$gl-col-padding: 15px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
@@ -261,7 +271,7 @@ $diff-view-modes-border: #c1c1c1;
/*
* Fonts
*/
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
+$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
/*
@@ -440,6 +450,7 @@ $logs-p-color: #333;
/*
* Forms
*/
+$input-height: 34px;
$input-danger-bg: #f2dede;
$input-danger-border: $red-400;
$input-group-addon-bg: #f7f8fa;
@@ -572,6 +583,12 @@ $stage-hover-border: #d1e7fc;
$action-icon-color: #d6d6d6;
/*
+Pipeline Schedules
+*/
+$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
+
+
+/*
Filtered Search
*/
$filter-name-resting-color: #f8f8f8;
@@ -591,3 +608,15 @@ Convdev Index
$color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
+
+/*
+Performance Bar
+*/
+$perf-bar-text: #999;
+$perf-bar-production: #222;
+$perf-bar-staging: #291430;
+$perf-bar-development: #4c1210;
+$perf-bar-bucket-bg: #111;
+$perf-bar-bucket-color: #ccc;
+$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
+$perf-bar-bucket-box-shadow-to: rgba($black, .25);
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 1c1392f8f67..b1ff2659131 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -3,6 +3,7 @@
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ margin-bottom: $gl-padding;
.well-segment {
padding: $gl-padding;
@@ -21,6 +22,11 @@
font-size: 12px;
}
}
+
+ &.admin-well h4 {
+ border-bottom: 1px solid $border-color;
+ padding-bottom: 8px;
+ }
}
.icon-container {
@@ -53,6 +59,14 @@
padding: 15px;
}
+.dark-well {
+ background-color: $gray-normal;
+
+ .btn {
+ width: 100%;
+ }
+}
+
.well-centered {
h1 {
font-weight: normal;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 441bfc479f6..73cb3a7cf4c 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -4,32 +4,29 @@
header.navbar-gitlab-new {
color: $white-light;
- background-color: $purple-900;
+ background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0;
.header-content {
padding-left: 0;
.title-container {
+ align-items: stretch;
padding-top: 0;
overflow: visible;
}
.title {
- display: block;
- height: 100%;
+ display: flex;
padding-right: 0;
color: currentColor;
> a {
display: flex;
align-items: center;
- height: 100%;
- padding-top: 3px;
padding-right: $gl-padding;
padding-left: $gl-padding;
margin-left: -$gl-padding;
- border-bottom: 3px solid transparent;
@media (min-width: $screen-sm-min) {
padding-right: $gl-padding;
@@ -46,9 +43,8 @@ header.navbar-gitlab-new {
&:hover,
&:focus {
- color: currentColor;
+ color: $tanuki-yellow;
text-decoration: none;
- border-bottom-color: $white-light;
}
}
}
@@ -72,7 +68,7 @@ header.navbar-gitlab-new {
.navbar-collapse {
padding-left: 0;
- color: $white-light;
+ color: $indigo-200;
box-shadow: 0;
@media (max-width: $screen-xs-max) {
@@ -102,7 +98,7 @@ header.navbar-gitlab-new {
font-size: 14px;
text-align: center;
color: currentColor;
- border-left: 1px solid lighten($purple-700, 10%);
+ border-left: 1px solid lighten($indigo-700, 10%);
&:hover,
&:focus,
@@ -121,6 +117,7 @@ header.navbar-gitlab-new {
li {
.badge {
box-shadow: none;
+ font-weight: 600;
}
}
}
@@ -134,12 +131,11 @@ header.navbar-gitlab-new {
> a {
background: none;
- opacity: .9;
- will-change: opacity;
+ will-change: color;
&.header-user-dropdown-toggle {
.header-user-avatar {
- border-color: $white-light;
+ border-color: $indigo-200;
}
}
@@ -166,29 +162,34 @@ header.navbar-gitlab-new {
.navbar-sub-nav {
display: flex;
margin-bottom: 0;
- color: $white-light;
+ color: $indigo-200;
> li {
- &.active > a,
- a:hover,
- a:focus {
- border-bottom-color: $white-light;
+ > a:hover,
+ > a:focus {
+ box-shadow: inset 0 -3px 0 rgba($indigo-200, .4);
text-decoration: none;
outline: 0;
- opacity: 1;
+ color: $white-light;
+ }
+
+ &.active > a {
+ box-shadow: inset 0 -3px 0 $indigo-500;
+ color: $white-light;
+ font-weight: 700;
}
> a {
display: block;
- padding: 16px 10px 13px;
+ padding: 16px 10px;
font-size: 13px;
color: currentColor;
- border-bottom: 3px solid transparent;
- opacity: .9;
- will-change: opacity;
+ box-shadow: inset 0 0 0 transparent;
+ will-change: box-shadow;
+ transition: box-shadow 0.15s;
@media (min-width: $screen-sm-min) {
- padding: 15px $gl-padding 12px;
+ padding: 15px $gl-padding;
font-size: 14px;
}
}
@@ -208,55 +209,60 @@ header.navbar-gitlab-new {
.search {
form {
- border-color: $purple-800;
+ border: 0;
+ background-color: rgba($indigo-200, .2);
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
- border-color: rgba($white-light, .6);
+ background-color: rgba($indigo-200, .3);
box-shadow: none;
}
}
&.search-active form {
- border-color: $white-light;
- }
-
- form,
- .search-input {
- background-color: $purple-700;
+ background-color: rgba($indigo-200, .3);
+ box-shadow: none;
}
.search-input {
color: $white-light;
+ background: none;
}
.search-input::placeholder {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
.location-badge {
font-size: 12px;
- color: rgba($white-light, .6);
- background-color: $purple-800;
+ color: $indigo-100;
+ background-color: rgba($indigo-200, .1);
transition: color 0.15s;
will-change: color;
+ margin: -4px 4px -4px -4px;
+ line-height: 25px;
+ padding: 4px 8px;
+ border-radius: 2px 0 0 2px;
+ border-right: 1px solid $indigo-800;
+ height: 34px;
}
.search-input-wrap {
.search-icon,
.clear-icon {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
}
&.search-active {
.location-badge {
color: $white-light;
- background-color: $purple-800;
+ background-color: rgba($indigo-200, .2);
}
.search-input-wrap {
.search-icon {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
.clear-icon {
@@ -265,3 +271,127 @@ header.navbar-gitlab-new {
}
}
}
+
+.breadcrumbs {
+ display: flex;
+ min-height: 60px;
+ padding-top: $gl-padding-top;
+ padding-bottom: $gl-padding-top;
+ color: $gl-text-color;
+ border-bottom: 1px solid $border-color;
+
+ .dropdown-toggle-caret {
+ position: relative;
+ top: -1px;
+ padding: 0 5px;
+ color: rgba($black, .65);
+ font-size: 10px;
+ line-height: 1;
+ background: none;
+ border: 0;
+
+ &:focus {
+ outline: 0;
+ }
+ }
+}
+
+.breadcrumbs-container {
+ display: flex;
+ width: 100%;
+ position: relative;
+
+ .dropdown-menu-projects {
+ margin-top: -$gl-padding;
+ margin-left: $gl-padding;
+ }
+}
+
+.breadcrumbs-links {
+ flex: 1;
+ align-self: center;
+ color: $black-transparent;
+
+ a {
+ color: rgba($black, .65);
+
+ &:not(:first-child),
+ &.group-path {
+ margin-left: 4px;
+ }
+
+ &:not(:last-of-type),
+ &.group-path {
+ margin-right: 3px;
+ }
+ }
+
+ .title {
+ white-space: nowrap;
+
+ > a {
+ &:last-of-type {
+ font-weight: 600;
+ }
+ }
+ }
+
+ .avatar-tile {
+ margin-right: 5px;
+ border: 1px solid $border-color;
+ border-radius: 50%;
+ vertical-align: sub;
+
+ &.identicon {
+ float: left;
+ width: 16px;
+ height: 16px;
+ margin-top: 2px;
+ font-size: 10px;
+ }
+ }
+
+ .text-expander {
+ margin-left: 4px;
+ margin-right: 4px;
+
+ > i {
+ position: relative;
+ top: 1px;
+ }
+ }
+}
+
+.breadcrumbs-extra {
+ flex: 0 0 auto;
+ margin-left: auto;
+}
+
+.breadcrumbs-sub-title {
+ margin: 2px 0 0;
+ font-size: 16px;
+ font-weight: normal;
+
+ ul {
+ margin: 0;
+ }
+
+ li {
+ display: inline-block;
+
+ &:not(:last-child) {
+ &::after {
+ content: "/";
+ margin: 0 2px 0 5px;
+ }
+ }
+
+ &:last-child a {
+ font-weight: 600;
+ }
+ }
+
+ a {
+ color: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
new file mode 100644
index 00000000000..96459fe31cc
--- /dev/null
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -0,0 +1,209 @@
+@import "framework/variables";
+@import 'framework/tw_bootstrap_variables';
+@import "bootstrap/variables";
+
+$active-background: rgba(0,0,0,.04);
+$active-border: $indigo-500;
+$active-color: $indigo-700;
+$active-hover-background: $active-background;
+$active-hover-color: $gl-text-color;
+$inactive-badge-background: rgba(0,0,0,.08);
+$hover-background: $indigo-700;
+$hover-color: $white-light;
+$inactive-color: $gl-text-color-secondary;
+$new-sidebar-width: 220px;
+
+.page-with-new-sidebar {
+ @media (min-width: $screen-sm-min) {
+ padding-left: $new-sidebar-width;
+ }
+
+ // Override position: absolute
+ .right-sidebar {
+ position: fixed;
+ height: 100%;
+ }
+}
+
+.context-header {
+ border-bottom: 1px solid $border-color;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ padding: 10px 16px 10px 10px;
+ color: $gl-text-color;
+
+ .avatar-container {
+ flex: 0 0 40px;
+ }
+
+ &:hover {
+ background-color: $hover-background;
+ color: $hover-color;
+ border-color: $hover-background;
+
+ .avatar-container {
+ border-color: transparent;
+ }
+
+ .settings-avatar {
+ background-color: $indigo-500;
+
+ i {
+ color: $hover-color;
+ }
+ }
+ }
+
+ .project-title,
+ .group-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+.settings-avatar {
+ background-color: $white-light;
+ transition: background-color 100ms linear;
+
+ i {
+ font-size: 20px;
+ width: 100%;
+ color: $gl-text-color-secondary;
+ text-align: center;
+ align-self: center;
+ transition: color 100ms linear;
+ }
+}
+
+.nav-sidebar {
+ position: fixed;
+ z-index: 400;
+ width: $new-sidebar-width;
+ transition: width $sidebar-transition-duration;
+ top: 50px;
+ bottom: 0;
+ left: 0;
+ overflow: auto;
+ background-color: $gray-normal;
+ box-shadow: inset -2px 0 0 $border-color;
+
+ a {
+ text-decoration: none;
+ }
+
+ ul {
+ padding-left: 0;
+ list-style: none;
+ }
+
+ li {
+ white-space: nowrap;
+
+ a {
+ display: block;
+ padding: 12px 16px;
+ color: $inactive-color;
+ }
+ }
+
+ li.active {
+ box-shadow: inset 4px 0 0 $active-border;
+
+ > a {
+ color: $active-color;
+ font-weight: 700;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ width: 0;
+ }
+}
+
+.sidebar-sub-level-items {
+ display: none;
+ padding-bottom: 8px;
+
+ > li {
+ a {
+ font-size: 12px;
+ padding: 8px 16px 8px 24px;
+
+ &:hover,
+ &:focus {
+ background: $active-hover-background;
+ color: $active-hover-color;
+ }
+ }
+
+ &.active {
+ a {
+ &,
+ &:hover,
+ &:focus {
+ background: $active-background;
+ color: $active-color;
+ }
+ }
+ }
+ }
+}
+
+.sidebar-top-level-items {
+ > li {
+ .badge {
+ float: right;
+ background-color: $inactive-badge-background;
+ color: $inactive-color;
+ }
+
+ &.active {
+ background: $active-background;
+
+ .badge {
+ color: $active-color;
+ font-weight: 600;
+ }
+
+ .sidebar-sub-level-items {
+ display: block;
+ }
+ }
+
+ > a:hover {
+ background-color: $hover-background;
+ color: $hover-color;
+
+ .badge {
+ transition: background-color 100ms linear, color 100ms linear;
+ background-color: $indigo-500;
+ color: $hover-color;
+ }
+ }
+ }
+}
+
+
+// Make issue boards full-height now that sub-nav is gone
+
+.boards-list {
+ height: calc(100vh - 50px);
+
+ @media (min-width: $screen-sm-min) {
+ height: 475px; // Needed for PhantomJS
+ // scss-lint:disable DuplicateProperty
+ height: calc(100vh - 120px);
+ // scss-lint:enable DuplicateProperty
+ }
+}
+
+
+// Change color of all horizontal tabs to match the new indigo color
+.nav-links li.active a {
+ border-bottom-color: $active-border;
+
+ .badge {
+ font-weight: 600;
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 7eee0a71c66..23c06eca3c3 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -37,65 +37,77 @@
}
.build-page {
- .sticky {
- position: absolute;
- left: 0;
- right: 0;
+ .build-trace-container {
+ position: relative;
}
- .build-trace-container {
- position: absolute;
- top: 225px;
- left: 15px;
- bottom: 10px;
+ .build-trace {
background: $black;
color: $gray-darkest;
- font-family: $monospace_font;
+ white-space: pre;
+ overflow-x: auto;
font-size: 12px;
+ border-radius: 0;
+ border: none;
- &.sidebar-expanded {
- right: 305px;
+ .bash {
+ display: block;
}
+ }
- &.sidebar-collapsed {
- right: 16px;
+ .top-bar {
+ height: 35px;
+ display: flex;
+ justify-content: flex-end;
+ background: $gray-light;
+ border: 1px solid $border-color;
+ color: $gl-text-color;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 50px;
+
+ &.affix {
+ top: 50px;
}
- code {
- background: $black;
- color: $gray-darkest;
+ // with sidebar
+ &.affix.sidebar-expanded {
+ right: 306px;
+ left: 16px;
}
- .top-bar {
- top: 0;
- height: 35px;
- display: flex;
- justify-content: flex-end;
- background: $gray-light;
- border: 1px solid $border-color;
- color: $gl-text-color;
+ // without sidebar
+ &.affix.sidebar-collapsed {
+ right: 16px;
+ left: 16px;
+ }
- .truncated-info {
- margin: 0 auto;
- align-self: center;
+ &.affix-top {
+ position: absolute;
+ right: 0;
+ left: 0;
+ }
- .truncated-info-size {
- margin: 0 5px;
- }
+ .truncated-info {
+ margin: 0 auto;
+ align-self: center;
- .raw-link {
- color: $gl-text-color;
- margin-left: 5px;
- text-decoration: underline;
- }
+ .truncated-info-size {
+ margin: 0 5px;
+ }
+
+ .raw-link {
+ color: $gl-text-color;
+ margin-left: 5px;
+ text-decoration: underline;
}
}
.controllers {
display: flex;
- align-self: center;
font-size: 15px;
- margin-bottom: 4px;
+ justify-content: center;
+ align-items: center;
svg {
height: 15px;
@@ -103,17 +115,9 @@
fill: $gl-text-color;
}
- .controllers-buttons,
- .btn-scroll {
- color: $gl-text-color;
- height: 15px;
- vertical-align: middle;
- padding: 0;
- width: 12px;
- }
-
.controllers-buttons {
- margin: 1px 10px;
+ color: $gl-text-color;
+ margin: 0 10px;
}
.btn-scroll.animate {
@@ -143,16 +147,6 @@
}
}
- .bash {
- top: 35px;
- left: 10px;
- bottom: 0;
- overflow-y: scroll;
- overflow-x: hidden;
- padding: 10px 20px 20px 5px;
- white-space: pre;
- }
-
.environment-information {
border: 1px solid $border-color;
padding: 8px $gl-padding 12px;
@@ -399,6 +393,7 @@
.build-light-text {
color: $gl-text-color-secondary;
+ word-wrap: break-word;
}
.build-gutter-toggle {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b58922626fa..55011e8a21b 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -20,8 +20,6 @@
}
.diff-content {
- overflow: auto;
- overflow-y: hidden;
background: $white-light;
color: $gl-text-color;
border-radius: 0 0 3px 3px;
@@ -476,6 +474,7 @@
height: 19px;
width: 19px;
margin-left: -15px;
+ z-index: 100;
&:hover {
.diff-comment-avatar,
@@ -491,7 +490,7 @@
transform: translateX((($i * $x-pos) - $x-pos));
&:hover {
- transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
+ transform: translateX((($i * $x-pos) - $x-pos));
}
}
}
@@ -542,6 +541,7 @@
height: 19px;
padding: 0;
transition: transform .1s ease-out;
+ z-index: 100;
svg {
position: absolute;
@@ -555,10 +555,6 @@
fill: $white-light;
}
- &:hover {
- transform: scale(1.2);
- }
-
&:focus {
outline: 0;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 1046ebfa2e2..00ebf4e26ac 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -140,23 +140,6 @@
}
}
-.prometheus-graph {
- text {
- fill: $gl-text-color;
- stroke-width: 0;
- }
-
- .label-axis-text,
- .text-metric-usage {
- fill: $black;
- font-weight: 500;
- }
-
- .legend-axis-text {
- fill: $black;
- }
-}
-
.x-axis path,
.y-axis path,
.label-x-axis-line,
@@ -204,7 +187,7 @@
}
.text-metric {
- font-weight: 600;
+ font-size: 12px;
}
.selected-metric-line {
@@ -214,20 +197,15 @@
.deployment-line {
stroke: $black;
- stroke-width: 2;
+ stroke-width: 1;
}
.deploy-info-text {
dominant-baseline: text-before-edge;
}
-.text-metric-bold {
- font-weight: 600;
-}
-
.prometheus-state {
margin-top: 10px;
- display: none;
.state-button-section {
margin-top: 10px;
@@ -242,3 +220,78 @@
width: 38px;
}
}
+
+.prometheus-panel {
+ margin-top: 20px;
+}
+
+.prometheus-svg-container {
+ position: relative;
+ height: 0;
+ 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: 600;
+ }
+
+ .label-axis-text,
+ .text-metric-usage {
+ fill: $black;
+ font-weight: 500;
+ font-size: 12px;
+ }
+
+ .legend-axis-text {
+ fill: $black;
+ }
+
+ .tick > text {
+ font-size: 12px;
+ }
+
+ .text-metric-title {
+ font-size: 12px;
+ }
+
+ .y-label-text,
+ .x-label-text {
+ fill: $gray-darkest;
+ }
+
+ .axis-tick {
+ stroke: $gray-darker;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ .label-axis-text,
+ .text-metric-usage,
+ .legend-axis-text {
+ font-size: 8px;
+ }
+
+ .tick > text {
+ font-size: 8px;
+ }
+ }
+}
+
+.prometheus-row {
+ h5 {
+ font-size: 16px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e3ebcc8af6c..56a4b53ed61 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -200,7 +200,6 @@
right: 0;
transition: width .3s;
background: $gray-light;
- padding: 0 20px;
z-index: 200;
overflow: hidden;
@@ -224,6 +223,10 @@
}
}
+ .issuable-sidebar {
+ padding: 0 20px;
+ }
+
.issuable-sidebar-header {
padding-top: 10px;
}
@@ -597,7 +600,38 @@
.issue-info-container {
-webkit-flex: 1;
flex: 1;
+ display: flex;
padding-right: $gl-padding;
+
+ .issue-main-info {
+ flex: 1 auto;
+ margin-right: 10px;
+ }
+
+ .issuable-meta {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ flex: 1 0 auto;
+
+ .controls {
+ margin-bottom: 2px;
+ line-height: 20px;
+ padding: 0;
+ }
+
+ .issue-updated-at {
+ line-height: 20px;
+ }
+ }
+
+ @media(max-width: $screen-xs-max) {
+ .issuable-meta {
+ .controls li {
+ margin-right: 0;
+ }
+ }
+ }
}
.issue-check {
@@ -609,6 +643,30 @@
vertical-align: text-top;
}
}
+
+ .issuable-milestone,
+ .issuable-info,
+ .task-status,
+ .issuable-updated-at {
+ font-weight: normal;
+ color: $gl-text-color-secondary;
+
+ a {
+ color: $gl-text-color;
+
+ .fa {
+ color: $gl-text-color-secondary;
+ }
+ }
+ }
+
+ @media(max-width: $screen-md-max) {
+ .task-status,
+ .issuable-due-date,
+ .project-ref-path {
+ display: none;
+ }
+ }
}
}
@@ -741,3 +799,28 @@
}
}
}
+
+.issuable-close-button,
+.issuable-close-toggle {
+ @include transition(border-color, color);
+}
+
+.issuable-close-dropdown {
+ .dropdown-menu {
+ min-width: 270px;
+ left: auto;
+ right: 0;
+ }
+
+ .description {
+ margin-bottom: 10px;
+
+ .text {
+ margin: 0;
+ }
+ }
+
+ .dropdown-toggle > .icon {
+ margin: 0 3px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index b158416b940..ee48f7a3626 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -279,5 +279,9 @@
.label-link {
display: inline-block;
- vertical-align: text-top;
+ vertical-align: top;
+
+ .label {
+ vertical-align: inherit;
+ }
}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index f21005895e4..e7c07ef67f0 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -54,8 +54,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
- width: 400px;
- max-width: 50%;
}
}
@@ -65,7 +63,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
- width: 100%;
margin-top: 3px;
}
}
@@ -81,18 +78,10 @@
.member-form-control {
@media (max-width: $screen-xs-max) {
- padding: 5px 0;
+ padding-bottom: 5px;
margin-left: 0;
margin-right: 0;
}
-
- @media (min-width: $screen-sm-min) {
- width: 50%;
- }
-
- .dropdown-menu-toggle {
- width: 100%;
- }
}
.member-access-text {
@@ -216,3 +205,102 @@
}
}
}
+
+.content-list.members-list li {
+ display: flex;
+ justify-content: space-between;
+
+ .list-item-name {
+ float: none;
+ display: flex;
+ flex: 1;
+ }
+
+ .user-info {
+ padding-right: 10px;
+ }
+
+ .member {
+ font-weight: bold;
+ overflow-wrap: break-word;
+ word-break: break-all;
+ }
+
+ .member-group-link {
+ display: inline-block;
+ }
+
+ .form-control {
+ width: inherit;
+ }
+
+ .btn {
+ align-self: flex-start;
+ }
+
+ .form-horizontal ~ .btn {
+ margin-right: 0;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+
+ .controls > .btn {
+ margin-left: 0;
+ margin-right: 0;
+ display: block;
+ }
+
+ .form-control {
+ width: 100%;
+ }
+
+ .member-access-text {
+ line-height: 0;
+ margin-left: 50px;
+ }
+
+ .member-controls {
+ margin-top: 5px;
+ }
+
+ .form-horizontal {
+ margin-top: 10px;
+ }
+ }
+}
+
+.panel-mobile {
+ .content-list.members-list li {
+ display: block;
+
+ .member-controls {
+ float: none;
+ display: block;
+ }
+
+ .dropdown-menu-toggle,
+ .dropdown-menu,
+ .form-control,
+ .list-item-name {
+ width: 100%;
+ }
+
+ .dropdown-menu {
+ margin-top: 0;
+ }
+
+ .form-horizontal {
+ display: block;
+ }
+
+ .member-form-control {
+ margin: 5px 0;
+ }
+
+ .btn {
+ width: 100%;
+ margin-left: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 59e0624d94e..7adf17dddb8 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -731,11 +731,11 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 100;
+ z-index: 200;
background-color: $white-light;
border-bottom: 1px solid $border-color;
- @media(min-width: $screen-sm-min) {
+ @media (min-width: $screen-sm-min) {
position: sticky;
position: -webkit-sticky;
}
@@ -770,6 +770,12 @@
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
+
+ .inner-page-scroll-tabs {
+ background-color: $white-light;
+ margin-left: -$gl-padding;
+ padding-left: $gl-padding;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 9877ed2cfd6..cdb1e65e4be 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -356,7 +356,6 @@
color: $white-light;
padding-right: 2px;
margin-top: 2px;
- pointer-events: none;
}
}
@@ -366,56 +365,6 @@
width: 298px;
}
- .description {
- display: inline-block;
- white-space: normal;
- margin-left: 8px;
- padding-right: 33px;
- }
-
- li {
- padding-top: 6px;
-
- & > a {
- margin: 0;
- padding: 0;
- color: inherit;
- border-radius: 0;
- text-overflow: inherit;
-
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
- }
-
- &:hover,
- &:focus {
- background-color: $dropdown-hover-color;
- color: $white-light;
- }
-
- &.droplab-item-selected i {
- visibility: visible;
- }
-
- i {
- visibility: hidden;
- }
- }
-
- i {
- display: inline-block;
- vertical-align: top;
- padding-top: 2px;
- }
-
- .divider {
- margin: 0 8px;
- padding: 0;
- border-top: $gray-darkest;
- }
@media (max-width: $screen-xs-max) {
display: flex;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 53d5cf2f7bc..303425041df 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -628,8 +628,14 @@ ul.notes {
* Line note button on the side of diffs
*/
+.line_holder .is-over:not(.no-comment-btn) {
+ .add-diff-note {
+ opacity: 1;
+ }
+}
+
.add-diff-note {
- display: none;
+ opacity: 0;
margin-top: -2px;
border-radius: 50%;
background: $white-light;
@@ -642,13 +648,11 @@ ul.notes {
width: 23px;
height: 23px;
border: 1px solid $blue-500;
- transition: transform .1s ease-in-out;
&:hover {
background: $blue-500;
border-color: $blue-600;
color: $white-light;
- transform: scale(1.15);
}
&:active {
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 595eb40fec7..dc719a6ba94 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -74,3 +74,84 @@
margin-right: 3px;
}
}
+
+.pipeline-variable-list {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+ clear: both;
+}
+
+.pipeline-variable-row {
+ display: flex;
+ align-items: flex-end;
+
+ &:not(:last-child) {
+ margin-bottom: $gl-btn-padding;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ padding-right: $gl-col-padding;
+ }
+
+ &:last-child {
+ & .pipeline-variable-row-remove-button {
+ display: none;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ & .pipeline-variable-value-input {
+ margin-right: $pipeline-variable-remove-button-width;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .pipeline-variable-row-body {
+ margin-right: $pipeline-variable-remove-button-width;
+ }
+ }
+ }
+}
+
+.pipeline-variable-row-body {
+ display: flex;
+ width: calc(75% - #{$gl-col-padding});
+ padding-left: $gl-col-padding;
+
+ @media (max-width: $screen-sm-max) {
+ width: 100%;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+}
+
+.pipeline-variable-key-input {
+ margin-right: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-bottom: $gl-btn-padding;
+ }
+}
+
+.pipeline-variable-row-remove-button {
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: $pipeline-variable-remove-button-width;
+ height: $input-height;
+ padding: 0;
+ background: transparent;
+ border: 0;
+ color: $gl-text-color-secondary;
+ @include transition(color);
+
+ &:hover,
+ &:focus {
+ outline: none;
+ color: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index c207159f606..235c475ff26 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -286,8 +286,7 @@ table.u2f-registrations {
}
.user-callout {
- margin: 0 auto;
- max-width: $screen-lg-min;
+ margin: 20px -5px 0;
.bordered-box {
border: 1px solid $blue-300;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 562ecbc6986..7d7c34115f9 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -377,6 +377,7 @@ a.deploy-project-label {
}
.breadcrumb.repo-breadcrumb {
+ flex: 1;
padding: 0;
background: transparent;
border: none;
@@ -482,11 +483,12 @@ a.deploy-project-label {
.project-stats {
font-size: 0;
text-align: center;
+ max-width: 100%;
+ border-bottom: 1px solid $border-color;
.nav {
padding-top: 12px;
padding-bottom: 12px;
- border-bottom: 1px solid $border-color;
}
.nav > li {
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 9b6ff237557..57c73295d1e 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -33,3 +33,20 @@
font-weight: normal;
}
}
+
+.admin-runner-btn-group-cell {
+ min-width: 150px;
+
+ .btn-sm {
+ padding: 4px 9px;
+ }
+
+ .btn-default {
+ color: $gl-text-color-secondary;
+ }
+
+ .fa-pause,
+ .fa-play {
+ font-size: 11px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index ce1a13c6afa..dc88cf3e699 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,4 +1,5 @@
.tree-holder {
+
.nav-block {
margin: 10px 0;
@@ -15,6 +16,11 @@
.btn-group {
margin-left: 10px;
}
+
+ .control {
+ float: left;
+ margin-left: 10px;
+ }
}
.tree-ref-holder {
@@ -70,7 +76,8 @@
}
.file-finder {
- width: 50%;
+ max-width: 500px;
+ width: 100%;
.file-finder-input {
width: 95%;
diff --git a/vendor/assets/stylesheets/peek.scss b/app/assets/stylesheets/performance_bar.scss
index f1845fb9044..2890b6b1e49 100644
--- a/vendor/assets/stylesheets/peek.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,47 +1,44 @@
-//= require peek/views/performance_bar
-//= require peek/views/rblineprof
-
-header.navbar-gitlab.with-peek {
- top: 35px;
-}
+@import "framework/variables";
+@import "peek/views/performance_bar";
+@import "peek/views/rblineprof";
#peek {
height: 35px;
- background: #000;
+ background: $black;
line-height: 35px;
- color: #999;
+ color: $perf-bar-text;
&.disabled {
display: none;
}
&.production {
- background-color: #222;
+ background-color: $perf-bar-production;
}
&.staging {
- background-color: #291430;
+ background-color: $perf-bar-staging;
}
&.development {
- background-color: #4c1210;
+ background-color: $perf-bar-development;
}
.wrapper {
- width: 800px;
+ width: 1000px;
margin: 0 auto;
}
// UI Elements
.bucket {
- background: #111;
+ background: $perf-bar-bucket-bg;
display: inline-block;
padding: 4px 6px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
line-height: 1;
- color: #ccc;
+ color: $perf-bar-bucket-color;
border-radius: 3px;
- box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 2px rgba(0,0,0,.25);
+ box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from, inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
.hidden {
display: none;
@@ -53,12 +50,14 @@ header.navbar-gitlab.with-peek {
}
strong {
- color: #fff;
+ color: $white-light;
}
table {
+ color: $black;
+
strong {
- color: #000;
+ color: $black;
}
}
@@ -90,5 +89,15 @@ header.navbar-gitlab.with-peek {
}
#modal-peek-pg-queries-content {
- color: #000;
+ color: $black;
+}
+
+.peek-rblineprof-file {
+ pre.duration {
+ width: 280px;
+ }
+
+ .data {
+ overflow: visible;
+ }
}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 2eac0cabf7a..ed13ead63f9 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -1,7 +1,9 @@
class AbuseReportsController < ApplicationController
+ before_action :set_user, only: [:new]
+
def new
@abuse_report = AbuseReport.new
- @abuse_report.user_id = params[:user_id]
+ @abuse_report.user_id = @user.id
@ref_url = params.fetch(:ref_url, '')
end
@@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController
user_id
))
end
+
+ def set_user
+ @user = User.find_by(id: params[:user_id])
+
+ if @user.nil?
+ redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted."
+ elsif @user.blocked?
+ redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked."
+ end
+ end
end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4d4b8a8425f..1cc060e4de8 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -71,6 +71,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
+
+ params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(
@@ -124,6 +126,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_port,
:metrics_sample_interval,
:metrics_timeout,
+ :performance_bar_allowed_group_id,
+ :performance_bar_enabled,
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index a1975c0e341..984d5398708 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -40,14 +40,14 @@ class Admin::ProjectsController < Admin::ApplicationController
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
- redirect_to admin_namespace_project_path(@project.namespace, @project)
+ redirect_to admin_project_path(@project)
end
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
redirect_to(
- admin_namespace_project_path(@project.namespace, @project),
+ admin_project_path(@project),
notice: 'Repository check was triggered.'
)
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 824ce845706..db7edbd619b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
- include Peek::Rblineprof::CustomControllerHelpers
+ include WithPerformanceBar
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
@@ -68,21 +68,6 @@ class ApplicationController < ActionController::Base
end
end
- def peek_enabled?
- return false unless Gitlab::PerformanceBar.enabled?
- return false unless current_user
-
- if RequestStore.active?
- if RequestStore.store.key?(:peek_enabled)
- RequestStore.store[:peek_enabled]
- else
- RequestStore.store[:peek_enabled] = cookies[:perf_bar_enabled].present?
- end
- else
- cookies[:perf_bar_enabled].present?
- end
- end
-
protected
# This filter handles both private tokens and personal access tokens
@@ -110,6 +95,8 @@ class ApplicationController < ActionController::Base
end
def log_exception(exception)
+ Raven.capture_exception(exception) if sentry_enabled?
+
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 1a9904bbe57..782f0be9c4a 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -78,8 +78,7 @@ module CreatesCommit
end
def new_merge_request_path
- new_namespace_project_merge_request_path(
- @project_to_commit_into.namespace,
+ project_new_merge_request_path(
@project_to_commit_into,
merge_request: {
source_project_id: @project_to_commit_into.id,
@@ -91,7 +90,7 @@ module CreatesCommit
end
def existing_merge_request_path
- namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ project_merge_request_path(@project, @merge_request)
end
def merge_request_exists?
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 650ec1e326a..e18778cdf80 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -1,6 +1,7 @@
module IssuableCollections
extend ActiveSupport::Concern
include SortingHelper
+ include Gitlab::IssuableMetadata
included do
helper_method :issues_finder
@@ -9,45 +10,12 @@ module IssuableCollections
private
- def issuable_meta_data(issuable_collection, collection_type)
- # map has to be used here since using pluck or select will
- # throw an error when ordering issuables by priority which inserts
- # a new order into the collection.
- # We cannot use reorder to not mess up the paginated collection.
- issuable_ids = issuable_collection.map(&:id)
-
- return {} if issuable_ids.empty?
-
- issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type)
- issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type)
- issuable_merge_requests_count =
- if collection_type == 'Issue'
- MergeRequestsClosingIssues.count_for_collection(issuable_ids)
- else
- []
- end
-
- issuable_ids.each_with_object({}) do |id, issuable_meta|
- downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
- upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
- notes = issuable_note_count.find { |notes| notes.noteable_id == id }
- merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
-
- issuable_meta[id] = Issuable::IssuableMeta.new(
- upvotes.try(:count).to_i,
- downvotes.try(:count).to_i,
- notes.try(:count).to_i,
- merge_requests.try(:last).to_i
- )
- end
- end
-
def issues_collection
issues_finder.execute.preload(:project, :author, :assignees, :labels, :milestone, project: :namespace)
end
def merge_requests_collection
- merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace)
+ merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits)
end
def issues_finder
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 47d9ae350ae..c6b1e443de6 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -70,7 +70,7 @@ module MembershipActions
def members_page_url
if membershipable.is_a?(Project)
- project_settings_members_path(membershipable)
+ project_project_members_path(membershipable)
else
polymorphic_url([membershipable, :members])
end
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 1ff785ac2ca..081f3336780 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -45,7 +45,7 @@ module MilestoneActions
def milestone_redirect_path
if @project
- namespace_project_milestone_path(@project.namespace, @project, @milestone)
+ project_milestone_path(@project, @milestone)
elsif @group
group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
else
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
index 0854c73a02f..0576f0e6e70 100644
--- a/app/controllers/concerns/repository_settings_redirect.rb
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -2,6 +2,6 @@ module RepositorySettingsRedirect
extend ActiveSupport::Concern
def redirect_to_repository_settings(project)
- redirect_to namespace_project_settings_repository_path(project.namespace, project)
+ redirect_to project_settings_repository_path(project)
end
end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index b68d76aeff0..ada0dde87fb 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -9,9 +9,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+ redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
- redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
end
end
@@ -25,7 +25,7 @@ module SpammableActions
def recaptcha_check_with_fallback(&fallback)
if spammable.valid?
- redirect_to spammable
+ redirect_to spammable_path
elsif render_recaptcha?
ensure_spam_config_loaded!
@@ -56,6 +56,10 @@ module SpammableActions
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
+ def spammable_path
+ raise NotImplementedError, "#{self.class} does not implement #{__method__}"
+ end
+
def authorize_submit_spammable!
access_denied! unless current_user.admin?
end
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
new file mode 100644
index 00000000000..ed253042701
--- /dev/null
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -0,0 +1,17 @@
+module WithPerformanceBar
+ extend ActiveSupport::Concern
+
+ included do
+ include Peek::Rblineprof::CustomControllerHelpers
+ end
+
+ def peek_enabled?
+ return false unless Gitlab::PerformanceBar.enabled?(current_user)
+
+ if RequestStore.active?
+ RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? }
+ else
+ cookies[:perf_bar_enabled].present?
+ end
+ end
+end
diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb
index dd1d46a68c7..9dcb3a0eb6d 100644
--- a/app/controllers/dashboard/labels_controller.rb
+++ b/app/controllers/dashboard/labels_controller.rb
@@ -1,9 +1,14 @@
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
- labels = LabelsFinder.new(current_user).execute
-
respond_to do |format|
format.json { render json: LabelSerializer.new.represent_appearance(labels) }
end
end
+
+ def labels
+ finder_params = { project_ids: projects.select(:id) }
+ labels = LabelsFinder.new(current_user, finder_params).execute
+
+ GlobalLabel.build_collection(labels)
+ end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index e52fa766044..5c10d7bc261 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -2,15 +2,18 @@ class Groups::MilestonesController < Groups::ApplicationController
include MilestoneActions
before_action :group_projects
- before_action :milestone, only: [:show, :update, :merge_requests, :participants, :labels]
- before_action :authorize_admin_milestones!, only: [:new, :create, :update]
+ before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels]
+ before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update]
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(@projects)
+ @milestone_states = GlobalMilestone.states_count(group_projects, group)
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
+ format.json do
+ render json: milestones.map { |m| m.for_display.slice(:title, :name) }
+ end
end
end
@@ -19,49 +22,41 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def create
- project_ids = params[:milestone][:project_ids].reject(&:blank?)
- title = milestone_params[:title]
+ @milestone = Milestones::CreateService.new(group, current_user, milestone_params).execute
- if create_milestones(project_ids)
- redirect_to milestone_path(title)
+ if @milestone.persisted?
+ redirect_to milestone_path
else
- render_new_with_error(project_ids.empty?)
+ render "new"
end
end
def show
end
- def update
- @milestone.milestones.each do |milestone|
- Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone)
- end
-
- redirect_back_or_default(default: milestone_path(@milestone.title))
+ def edit
+ render_404 if @milestone.is_legacy_group_milestone?
end
- private
-
- def create_milestones(project_ids)
- return false unless project_ids.present?
+ def update
+ # Keep this compatible with legacy group milestones where we have to update
+ # all projects milestones states at once.
+ if @milestone.is_legacy_group_milestone?
+ update_params = milestone_params.select { |key| key == "state_event" }
+ milestones = @milestone.milestones
+ else
+ update_params = milestone_params
+ milestones = [@milestone]
+ end
- ActiveRecord::Base.transaction do
- @projects.where(id: project_ids).each do |project|
- Milestones::CreateService.new(project, current_user, milestone_params).execute
- end
+ milestones.each do |milestone|
+ Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
end
- true
- rescue ActiveRecord::ActiveRecordError => e
- flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}"
- false
+ redirect_to milestone_path
end
- def render_new_with_error(empty_project_ids)
- @milestone = Milestone.new(milestone_params)
- @milestone.errors.add(:base, "Please select at least one project.") if empty_project_ids
- render :new
- end
+ private
def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
@@ -71,16 +66,31 @@ class Groups::MilestonesController < Groups::ApplicationController
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
- def milestone_path(title)
- group_milestone_path(@group, title.to_slug.to_s, title: title)
+ def milestone_path
+ if @milestone.is_legacy_group_milestone?
+ group_milestone_path(group, @milestone.safe_title, title: @milestone.title)
+ else
+ group_milestone_path(group, @milestone.iid)
+ end
end
def milestones
- @milestones = GroupMilestone.build_collection(@group, @projects, params)
+ search_params = params.merge(group_ids: group.id)
+
+ milestones = MilestonesFinder.new(search_params).execute
+ legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
+
+ milestones + legacy_milestones
end
def milestone
- @milestone = GroupMilestone.build(@group, @projects, params[:title])
+ @milestone =
+ if params[:title]
+ GroupMilestone.build(group, group_projects, params[:title])
+ else
+ group.milestones.find_by_iid(params[:id])
+ end
+
render_404 unless @milestone
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
new file mode 100644
index 00000000000..0142ad8278c
--- /dev/null
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -0,0 +1,24 @@
+module Groups
+ module Settings
+ class CiCdController < Groups::ApplicationController
+ before_action :authorize_admin_pipeline!
+
+ def show
+ define_secret_variables
+ end
+
+ private
+
+ def define_secret_variables
+ @variable = Ci::GroupVariable.new(group: group)
+ .present(current_user: current_user)
+ @variables = group.variables.order_key_asc
+ .map { |variable| variable.present(current_user: current_user) }
+ end
+
+ def authorize_admin_pipeline!
+ return render_404 unless can?(current_user, :admin_pipeline, group)
+ end
+ end
+ end
+end
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
new file mode 100644
index 00000000000..10038ff3ad9
--- /dev/null
+++ b/app/controllers/groups/variables_controller.rb
@@ -0,0 +1,64 @@
+module Groups
+ class VariablesController < Groups::ApplicationController
+ before_action :variable, only: [:show, :update, :destroy]
+ before_action :authorize_admin_build!
+
+ def index
+ redirect_to group_settings_ci_cd_path(group)
+ end
+
+ def show
+ end
+
+ def update
+ if variable.update(variable_params)
+ redirect_to group_variables_path(group),
+ notice: 'Variable was successfully updated.'
+ else
+ render "show"
+ end
+ end
+
+ def create
+ @variable = group.variables.create(variable_params)
+ .present(current_user: current_user)
+
+ if @variable.persisted?
+ redirect_to group_settings_ci_cd_path(group),
+ notice: 'Variable was successfully created.'
+ else
+ render "show"
+ end
+ end
+
+ def destroy
+ if variable.destroy
+ redirect_to group_settings_ci_cd_path(group),
+ status: 302,
+ notice: 'Variable was successfully removed.'
+ else
+ redirect_to group_settings_ci_cd_path(group),
+ status: 302,
+ notice: 'Failed to remove the variable.'
+ end
+ end
+
+ private
+
+ def variable_params
+ params.require(:variable).permit(*variable_params_attributes)
+ end
+
+ def variable_params_attributes
+ %i[key value protected]
+ end
+
+ def variable
+ @variable ||= group.variables.find(params[:id]).present(current_user: current_user)
+ end
+
+ def authorize_admin_build!
+ return render_404 unless can?(current_user, :admin_build, group)
+ end
+ end
+end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 7625187c7be..0982a61902b 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -63,7 +63,7 @@ class InvitesController < ApplicationController
when Project
project = member.source
label = "project #{project.name_with_namespace}"
- path = namespace_project_path(project.namespace, project)
+ path = project_path(project)
when Group
group = member.source
label = "group #{group.name}"
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index b82681b197e..323d5d26eb6 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,5 +1,6 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
+ include Devise::Controllers::Rememberable
protect_from_forgery except: [:kerberos, :saml, :cas3]
@@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled?
+ params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user)
else
+ remember_me(@user) if remember_me?
sign_in_and_redirect(@user)
end
else
@@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
+
+ def remember_me?
+ request_params = request.env['omniauth.params']
+ (request_params['remember_me'] == '1') if request_params.present?
+ end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 3d7ce4f0222..95de3a44641 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -76,13 +76,13 @@ class Projects::ApplicationController < ApplicationController
def require_non_empty_project
# Be sure to return status code 303 to avoid a double DELETE:
# http://api.rubyonrails.org/classes/ActionController/Redirecting.html
- redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
+ redirect_to project_path(@project), status: 303 if @project.empty_repo?
end
def require_branch_head
unless @repository.branch_exists?(@ref)
redirect_to(
- namespace_project_tree_path(@project.namespace, @project, @ref),
+ project_tree_path(@project, @ref),
notice: "This action is not allowed unless you are on a branch"
)
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index ea036b1f705..f637a9a803b 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def keep
build.keep_artifacts!
- redirect_to namespace_project_job_path(project.namespace, project, build)
+ redirect_to project_job_path(project, build)
end
def latest_succeeded
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index a82d6fd5a4a..49ea2945675 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -27,9 +27,9 @@ class Projects::BlobController < Projects::ApplicationController
def create
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
- success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
+ success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
- failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
+ failure_path: project_new_blob_path(@project, @ref))
end
def show
@@ -63,7 +63,7 @@ class Projects::BlobController < Projects::ApplicationController
@path = params[:file_path] if params[:file_path].present?
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
- failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+ failure_path: project_blob_path(@project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
@@ -83,9 +83,9 @@ class Projects::BlobController < Projects::ApplicationController
def destroy
create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
- success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
+ success_path: -> { project_tree_path(@project, @branch_name) },
failure_view: :show,
- failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+ failure_path: project_blob_path(@project, @id))
end
def diff
@@ -118,7 +118,7 @@ class Projects::BlobController < Projects::ApplicationController
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
- return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
+ return redirect_to project_tree_path(@project, File.join(@ref, @path))
end
end
@@ -143,10 +143,10 @@ class Projects::BlobController < Projects::ApplicationController
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @branch_name == @ref
- diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
+ diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
- namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
+ project_blob_path(@project, File.join(@branch_name, @path))
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 94a752c21eb..86058531179 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -52,7 +52,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name)
else
- redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name)
+ redirect_to project_tree_path(@project, branch_name)
end
else
@error = result[:message]
@@ -62,7 +62,7 @@ class Projects::BranchesController < Projects::ApplicationController
format.json do
if result[:status] == :success
- render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) }
+ render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
else
render json: result[:messsage], status: :unprocessable_entity
end
@@ -79,7 +79,7 @@ class Projects::BranchesController < Projects::ApplicationController
flash_type = result[:status] == :error ? :alert : :notice
flash[flash_type] = result[:message]
- redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303
+ redirect_to project_branches_path(@project), status: 303
end
format.js { render nothing: true, status: result[:return_code] }
@@ -90,7 +90,7 @@ class Projects::BranchesController < Projects::ApplicationController
def destroy_all_merged
DeleteMergedBranchesService.new(@project, current_user).async_execute
- redirect_to namespace_project_branches_path(@project.namespace, @project),
+ redirect_to project_branches_path(@project),
notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
end
@@ -106,8 +106,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def url_to_autodeploy_setup(project, branch_name)
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
branch_name,
file_name: '.gitlab-ci.yml',
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index f34a198634e..b45e5d7ff43 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -7,23 +7,23 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
before_action :validate_artifacts!
def download
- redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ redirect_to download_project_job_artifacts_path(project, job)
end
def browse
- redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to browse_project_job_artifacts_path(project, job, path: params[:path])
end
def file
- redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to file_project_job_artifacts_path(project, job, path: params[:path])
end
def raw
- redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to raw_project_job_artifacts_path(project, job, path: params[:path])
end
def latest_succeeded
- redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
+ redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
end
private
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 1334a231788..230b072dcea 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -2,15 +2,15 @@ class Projects::BuildsController < Projects::ApplicationController
before_action :authorize_read_build!
def index
- redirect_to namespace_project_jobs_path(project.namespace, project)
+ redirect_to project_jobs_path(project)
end
def show
- redirect_to namespace_project_job_path(project.namespace, project, job)
+ redirect_to project_job_path(project, job)
end
def raw
- redirect_to raw_namespace_project_job_path(project.namespace, project, job)
+ redirect_to raw_project_job_path(project, job)
end
private
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 7c3cce1c241..14a1e11a6ea 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -80,16 +80,16 @@ class Projects::CommitController < Projects::ApplicationController
end
def successful_change_path
- referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
+ referenced_merge_request_url || project_commits_url(@project, @branch_name)
end
def failed_change_path
- referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
+ referenced_merge_request_url || project_commit_url(@project, params[:id])
end
def referenced_merge_request_url
if merge_request = @commit.merged_merge_request(current_user)
- namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request)
+ project_merge_request_url(merge_request.target_project, merge_request)
end
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index ef400c4d745..c8613c0d634 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController
from: params[:from].presence,
to: params[:to].presence
}
- redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars)
+ redirect_to project_compare_index_path(@project, from_to_vars)
else
- redirect_to namespace_project_compare_path(@project.namespace, @project,
+ redirect_to project_compare_path(@project,
params[:from], params[:to])
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index f88a1ffd1e9..29e223a5273 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
+ Gitlab::PollingInterval.set_header(response, interval: 3_000)
+
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
@@ -63,7 +65,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment = project.environments.create(environment_params)
if @environment.persisted?
- redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ redirect_to project_environment_path(project, @environment)
else
render :new
end
@@ -71,7 +73,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def update
if @environment.update(environment_params)
- redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ redirect_to project_environment_path(project, @environment)
else
render :edit
end
@@ -86,7 +88,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
if stop_action
polymorphic_url([project.namespace.becomes(Namespace), project, stop_action])
else
- namespace_project_environment_url(project.namespace, project, @environment)
+ project_environment_url(project, @environment)
end
respond_to do |format|
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 1eb3800e49d..3f83bef2c79 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController
if @forked_project.saved? && @forked_project.forked?
if @forked_project.import_in_progress?
- redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
+ redirect_to project_import_path(@forked_project, continue: continue_params)
else
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
+ redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
end
end
else
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index df5221fe95f..57372f9e79d 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -29,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def ci
- redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project)
+ redirect_to charts_project_pipelines_path(@project)
end
private
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index deb33a2f0ff..f59200d3b1f 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = 'Please select a group.'
end
- redirect_to namespace_project_settings_members_path(project.namespace, project)
+ redirect_to project_project_members_path(project)
end
def update
@@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_settings_members_path(project.namespace, project), status: 302
+ redirect_to project_project_members_path(project), status: 302
end
format.js { head :ok }
end
diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb
index 354f0d6db3a..b9c4b29580a 100644
--- a/app/controllers/projects/hook_logs_controller.rb
+++ b/app/controllers/projects/hook_logs_controller.rb
@@ -18,7 +18,7 @@ class Projects::HookLogsController < Projects::ApplicationController
set_hook_execution_notice(status, message)
- redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
+ redirect_to edit_project_hook_path(@project, @hook)
end
private
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index f5143280154..18895c3f0f3 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -17,7 +17,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe
end
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+ redirect_to project_settings_integrations_path(@project)
end
def edit
@@ -26,7 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update_attributes(hook_params)
flash[:notice] = 'Hook was successfully updated.'
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+ redirect_to project_settings_integrations_path(@project)
else
render 'edit'
end
@@ -47,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController
def destroy
hook.destroy
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project), status: 302
+ redirect_to project_settings_integrations_path(@project), status: 302
end
private
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 4b143434ea5..49aa32119ef 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -17,7 +17,7 @@ class Projects::ImportsController < Projects::ApplicationController
@project.reload.import_schedule
end
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
end
def show
@@ -25,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
+ redirect_to project_path(@project), notice: finished_notice
end
elsif @project.import_failed?
- redirect_to new_namespace_project_import_path(@project.namespace, @project)
+ redirect_to new_project_import_path(@project)
else
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
@@ -50,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo
if @project.repository_exists?
- redirect_to namespace_project_path(@project.namespace, @project)
+ redirect_to project_path(@project)
end
end
def redirect_if_progress
if @project.import_in_progress?
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
end
end
def redirect_if_no_import
if @project.repository_exists? && @project.no_import?
- redirect_to namespace_project_path(@project.namespace, @project)
+ redirect_to project_path(@project)
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index dfc6baa34a4..c9e636fb65e 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -227,7 +227,7 @@ 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!
+ @noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
return render_404 unless can?(current_user, :read_issue, @issue)
@@ -238,6 +238,10 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :issue
alias_method :spammable, :issue
+ def spammable_path
+ project_issue_path(@project, @issue)
+ end
+
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
end
@@ -267,10 +271,22 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issue_params
- params.require(:issue).permit(
- :title, :assignee_id, :position, :description, :confidential,
- :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: []
- )
+ params.require(:issue).permit(*issue_params_attributes)
+ end
+
+ def issue_params_attributes
+ %i[
+ title
+ assignee_id
+ position
+ description
+ confidential
+ milestone_id
+ due_date
+ state_event
+ task_num
+ lock_version
+ ] + [{ label_ids: [], assignee_ids: [] }]
end
def authenticate_user!
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index cb4f46388fd..96abdac91b6 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -38,7 +38,7 @@ class Projects::JobsController < Projects::ApplicationController
build.cancel if can?(current_user, :update_build, build)
end
- redirect_to namespace_project_jobs_path(project.namespace, project)
+ redirect_to project_jobs_path(project)
end
def show
@@ -108,7 +108,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
- redirect_to namespace_project_job_path(project.namespace, project, @build),
+ redirect_to project_job_path(project, @build),
notice: "Build has been successfully erased!"
else
respond_422
@@ -137,6 +137,6 @@ class Projects::JobsController < Projects::ApplicationController
end
def build_path(build)
- namespace_project_job_path(build.project.namespace, build.project, build)
+ project_job_path(build.project, build)
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index daa973c9281..480a2dff262 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController
if @label.valid?
respond_to do |format|
- format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) }
+ format.html { redirect_to project_labels_path(@project) }
format.json { render json: @label }
end
else
@@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
- redirect_to namespace_project_labels_path(@project.namespace, @project)
+ redirect_to project_labels_path(@project)
else
render :edit
end
@@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController
Gitlab::IssuesLabels.generate(@project)
if params[:redirect] == 'issues'
- redirect_to namespace_project_issues_path(@project.namespace, @project)
+ redirect_to project_issues_path(@project)
elsif params[:redirect] == 'merge_requests'
- redirect_to namespace_project_merge_requests_path(@project.namespace,
- @project)
+ redirect_to project_merge_requests_path(@project)
else
- redirect_to namespace_project_labels_path(@project.namespace, @project)
+ redirect_to project_labels_path(@project)
end
end
@@ -74,7 +73,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
@labels = find_labels
- redirect_to namespace_project_labels_path(@project.namespace, @project),
+ redirect_to project_labels_path(@project),
status: 302,
notice: 'Label was removed'
end
@@ -114,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
- redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ redirect_to(project_labels_path(@project),
notice: 'Label was promoted to a Group Label')
end
format.js
@@ -125,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ redirect_to(project_labels_path(@project),
notice: 'Failed to promote label due to internal error. Please contact administrators.')
end
format.js
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 38f7e6eb5e9..0f6add3e287 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController
if result
flash[:notice] = 'This service is now configured'
- redirect_to edit_namespace_project_service_path(
- @project.namespace, @project, service)
+ redirect_to edit_project_service_path(@project, service)
else
flash[:alert] = message || 'Failed to configure service'
- redirect_to new_namespace_project_mattermost_path(
- @project.namespace, @project)
+ redirect_to new_project_mattermost_path(@project)
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
new file mode 100644
index 00000000000..6602b204fcb
--- /dev/null
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -0,0 +1,47 @@
+class Projects::MergeRequests::ApplicationController < Projects::ApplicationController
+ before_action :check_merge_requests_available!
+ before_action :merge_request
+ before_action :authorize_read_merge_request!
+ before_action :ensure_ref_fetched
+
+ private
+
+ def merge_request
+ @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
+ end
+
+ # Make sure merge requests created before 8.0
+ # have head file in refs/merge-requests/
+ def ensure_ref_fetched
+ @merge_request.ensure_ref_fetched
+ end
+
+ def merge_request_params
+ params.require(:merge_request).permit(merge_request_params_attributes)
+ end
+
+ def merge_request_params_attributes
+ [
+ :assignee_id,
+ :description,
+ :force_remove_source_branch,
+ :lock_version,
+ :milestone_id,
+ :source_branch,
+ :source_project_id,
+ :state_event,
+ :target_branch,
+ :target_project_id,
+ :task_num,
+ :title,
+
+ label_ids: []
+ ]
+ end
+
+ def set_pipeline_variables
+ @pipelines = @merge_request.all_pipelines
+ @pipeline = @merge_request.head_pipeline
+ @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
+ end
+end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
new file mode 100644
index 00000000000..28afef101a9
--- /dev/null
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -0,0 +1,66 @@
+class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::ApplicationController
+ include IssuableActions
+
+ before_action :authorize_can_resolve_conflicts!
+
+ def show
+ respond_to do |format|
+ format.html do
+ labels
+ end
+
+ format.json do
+ if @conflicts_list.can_be_resolved_in_ui?
+ render json: @conflicts_list
+ elsif @merge_request.can_be_merged?
+ render json: {
+ message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
+ type: 'error'
+ }
+ else
+ render json: {
+ message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
+ type: 'error'
+ }
+ end
+ end
+ end
+ end
+
+ def conflict_for_path
+ return render_404 unless @conflicts_list.can_be_resolved_in_ui?
+
+ file = @conflicts_list.file_for_path(params[:old_path], params[:new_path])
+
+ return render_404 unless file
+
+ render json: file, full_content: true
+ end
+
+ def resolve_conflicts
+ return render_404 unless @conflicts_list.can_be_resolved_in_ui?
+
+ if @merge_request.can_be_merged?
+ render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
+ return
+ end
+
+ begin
+ ::MergeRequests::Conflicts::ResolveService
+ .new(merge_request)
+ .execute(current_user, params)
+
+ flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+
+ render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
+ rescue Gitlab::Conflict::ResolutionError => e
+ render status: :bad_request, json: { message: e.message }
+ end
+ end
+
+ def authorize_can_resolve_conflicts!
+ @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
+
+ return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
+ end
+end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
new file mode 100644
index 00000000000..da058da795e
--- /dev/null
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -0,0 +1,128 @@
+class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
+ include DiffForPath
+ include DiffHelper
+
+ skip_before_action :merge_request
+ skip_before_action :ensure_ref_fetched
+ before_action :authorize_create_merge_request!
+ before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
+ before_action :build_merge_request, except: [:create]
+
+ def new
+ define_new_vars
+ end
+
+ def create
+ @target_branches ||= []
+ @merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
+
+ if @merge_request.valid?
+ redirect_to(merge_request_path(@merge_request))
+ else
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+
+ define_new_vars
+ render action: "new"
+ end
+ end
+
+ def pipelines
+ @pipelines = @merge_request.all_pipelines
+
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+ render json: {
+ pipelines: PipelineSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(@pipelines)
+ }
+ end
+
+ def diffs
+ @diffs = if @merge_request.can_be_created
+ @merge_request.diffs(diff_options)
+ else
+ []
+ end
+ @diff_notes_disabled = true
+
+ @environment = @merge_request.environments_for(current_user).last
+
+ render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) }
+ end
+
+ def diff_for_path
+ @diffs = @merge_request.diffs(diff_options)
+ @diff_notes_disabled = true
+
+ render_diff_for_path(@diffs)
+ end
+
+ def branch_from
+ # This is always source
+ @source_project = @merge_request.nil? ? @project : @merge_request.source_project
+
+ if params[:ref].present?
+ @ref = params[:ref]
+ @commit = @repository.commit("refs/heads/#{@ref}")
+ end
+
+ render layout: false
+ end
+
+ def branch_to
+ @target_project = selected_target_project
+
+ if params[:ref].present?
+ @ref = params[:ref]
+ @commit = @target_project.commit("refs/heads/#{@ref}")
+ end
+
+ render layout: false
+ end
+
+ def update_branches
+ @target_project = selected_target_project
+ @target_branches = @target_project.repository.branch_names
+
+ render layout: false
+ end
+
+ private
+
+ def build_merge_request
+ params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+ @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
+ end
+
+ def define_new_vars
+ @noteable = @merge_request
+
+ @target_branches = if @merge_request.target_project
+ @merge_request.target_project.repository.branch_names
+ else
+ []
+ end
+
+ @target_project = @merge_request.target_project
+ @source_project = @merge_request.source_project
+ @commits = @merge_request.compare_commits.reverse
+ @commit = @merge_request.diff_head_commit
+
+ @note_counts = Note.where(commit_id: @commits.map(&:id))
+ .group(:commit_id).count
+
+ @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
+
+ set_pipeline_variables
+ end
+
+ def selected_target_project
+ if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
+ @project
+ else
+ @project.forked_project_link.forked_from_project
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
new file mode 100644
index 00000000000..330b7df4541
--- /dev/null
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -0,0 +1,66 @@
+class Projects::MergeRequests::DiffsController < Projects::MergeRequests::ApplicationController
+ include DiffForPath
+ include DiffHelper
+ include RendersNotes
+
+ before_action :apply_diff_view_cookie!
+ before_action :define_diff_vars
+ before_action :define_diff_comment_vars
+
+ def show
+ @environment = @merge_request.environments_for(current_user).last
+
+ render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
+ end
+
+ def diff_for_path
+ render_diff_for_path(@diffs)
+ end
+
+ private
+
+ def define_diff_vars
+ @merge_request_diff =
+ if params[:diff_id]
+ @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
+ else
+ @merge_request.merge_request_diff
+ end
+
+ @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
+ @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+ if params[:start_sha].present?
+ @start_sha = params[:start_sha]
+ @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+ unless @start_version
+ @start_sha = @merge_request_diff.head_commit_sha
+ @start_version = @merge_request_diff
+ end
+ end
+
+ @compare =
+ if @start_sha
+ @merge_request_diff.compare_with(@start_sha)
+ else
+ @merge_request_diff
+ end
+
+ @diffs = @compare.diffs(diff_options)
+ end
+
+ def define_diff_comment_vars
+ @new_diff_note_attrs = {
+ noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id
+ }
+
+ @diff_notes_disabled = false
+
+ @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
+
+ @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
+ @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 879ff6d393e..a573b392591 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,38 +1,17 @@
-class Projects::MergeRequestsController < Projects::ApplicationController
+class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationController
include ToggleSubscriptionAction
- include DiffForPath
- include DiffHelper
include IssuableActions
include RendersNotes
include ToggleAwardEmoji
include IssuableCollections
- before_action :check_merge_requests_available!
- before_action :merge_request, only: [
- :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge,
- :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues, :commit_change_content
- ]
- before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
- before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
- before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
- before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
- before_action :check_if_can_be_merged, only: :show
- before_action :apply_diff_view_cookie!, only: [:new_diffs]
- before_action :build_merge_request, only: [:new, :new_diffs]
-
- # Allow read any merge_request
- before_action :authorize_read_merge_request!
-
- # Allow write(create) merge_request
- before_action :authorize_create_merge_request!, only: [:new, :create]
-
- # Allow modify merge_request
+ 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 :authenticate_user!, only: [:assign_related_issues]
- before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts]
-
def index
@collection_type = "MergeRequest"
@merge_requests = merge_requests_collection
@@ -72,10 +51,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def show
+ validates_merge_request
+ ensure_ref_fetched
+ close_merge_request_without_source_project
+ check_if_can_be_merged
+
respond_to do |format|
format.html do
- define_discussion_vars
- define_show_vars
+ # Build a note object for comment form
+ @note = @project.notes.new(noteable: @merge_request)
+
+ @discussions = @merge_request.discussions
+ @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
+
+ @noteable = @merge_request
+ @commits_count = @merge_request.commits_count
+
+ if @merge_request.locked_long_ago?
+ @merge_request.unlock_mr
+ @merge_request.close
+ end
+
+ labels
+
+ set_pipeline_variables
end
format.json do
@@ -98,198 +97,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- def diffs
- apply_diff_view_cookie!
-
- respond_to do |format|
- format.html { define_discussion_vars }
- format.json do
- define_diff_vars
- define_diff_comment_vars
-
- @environment = @merge_request.environments_for(current_user).last
-
- render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
- end
- end
- end
-
- # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new
- # and uses that (unsaved) MR.
- #
- def diff_for_path
- if params[:id]
- merge_request
- define_diff_vars
- define_diff_comment_vars
- else
- build_merge_request
- @compare = @merge_request
- @diffs = @compare.diffs(diff_options)
- @diff_notes_disabled = true
- end
-
- render_diff_for_path(@diffs)
- end
-
def commits
- respond_to do |format|
- format.html do
- define_discussion_vars
-
- render 'show'
- end
- format.json do
- # Get commits from repository
- # or from cache if already merged
- @commits = @merge_request.commits
- @note_counts = Note.where(commit_id: @commits.map(&:id))
- .group(:commit_id).count
-
- render json: { html: view_to_html_string('projects/merge_requests/show/_commits') }
- end
- end
- end
-
- def conflicts
- respond_to do |format|
- format.html { define_discussion_vars }
-
- format.json do
- if @conflicts_list.can_be_resolved_in_ui?
- render json: @conflicts_list
- elsif @merge_request.can_be_merged?
- render json: {
- message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
- type: 'error'
- }
- else
- render json: {
- message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
- type: 'error'
- }
- end
- end
- end
- end
-
- def conflict_for_path
- return render_404 unless @conflicts_list.can_be_resolved_in_ui?
-
- file = @conflicts_list.file_for_path(params[:old_path], params[:new_path])
-
- return render_404 unless file
-
- render json: file, full_content: true
- end
-
- def resolve_conflicts
- return render_404 unless @conflicts_list.can_be_resolved_in_ui?
-
- if @merge_request.can_be_merged?
- render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
- return
- end
-
- begin
- MergeRequests::Conflicts::ResolveService
- .new(merge_request)
- .execute(current_user, params)
-
- flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+ # Get commits from repository
+ # or from cache if already merged
+ @commits = @merge_request.commits
+ @note_counts = Note.where(commit_id: @commits.map(&:id))
+ .group(:commit_id).count
- render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) }
- rescue Gitlab::Conflict::ResolutionError => e
- render status: :bad_request, json: { message: e.message }
- end
+ render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end
def pipelines
@pipelines = @merge_request.all_pipelines
- respond_to do |format|
- format.html do
- define_discussion_vars
-
- render 'show'
- end
-
- format.json do
- Gitlab::PollingInterval.set_header(response, interval: 10_000)
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
- render json: PipelineSerializer
- .new(project: @project, current_user: @current_user)
- .represent(@pipelines)
- end
- end
- end
-
- def new
- respond_to do |format|
- format.html { define_new_vars }
- format.json do
- define_pipelines_vars
-
- Gitlab::PollingInterval.set_header(response, interval: 10_000)
-
- render json: {
- pipelines: PipelineSerializer
- .new(project: @project, current_user: @current_user)
- .represent(@pipelines)
- }
- end
- end
- end
-
- def new_diffs
- respond_to do |format|
- format.html do
- define_new_vars
- @show_changes_tab = true
- render "new"
- end
- format.json do
- @diffs = if @merge_request.can_be_created
- @merge_request.diffs(diff_options)
- else
- []
- end
- @diff_notes_disabled = true
-
- @environment = @merge_request.environments_for(current_user).last
-
- render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs, environment: @environment) }
- end
- end
- end
-
- def create
- @target_branches ||= []
- @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
-
- if @merge_request.valid?
- redirect_to(merge_request_path(@merge_request))
- else
- @source_project = @merge_request.source_project
- @target_project = @merge_request.target_project
- render action: "new"
- end
+ render json: PipelineSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(@pipelines)
end
def edit
- @source_project = @merge_request.source_project
- @target_project = @merge_request.target_project
- @target_branches = @merge_request.target_project.repository.branch_names
+ define_edit_vars
end
def update
- @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
+ @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
respond_to do |format|
format.html do
if @merge_request.valid?
redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
else
+ define_edit_vars
+
render :edit
end
end
@@ -299,11 +140,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
rescue ActiveRecord::StaleObjectError
+ define_edit_vars if request.format.html?
+
render_conflict_response
end
def remove_wip
- @merge_request = MergeRequests::UpdateService
+ @merge_request = ::MergeRequests::UpdateService
.new(project, current_user, wip_event: 'unwip')
.execute(@merge_request)
@@ -319,7 +162,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied!
end
- MergeRequests::MergeWhenPipelineSucceedsService
+ ::MergeRequests::MergeWhenPipelineSucceedsService
.new(@project, current_user)
.cancel(@merge_request)
@@ -338,53 +181,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- def branch_from
- # This is always source
- @source_project = @merge_request.nil? ? @project : @merge_request.source_project
-
- if params[:ref].present?
- @ref = params[:ref]
- @commit = @repository.commit("refs/heads/#{@ref}")
- end
-
- render layout: false
- end
-
- def branch_to
- @target_project = selected_target_project
-
- if params[:ref].present?
- @ref = params[:ref]
- @commit = @target_project.commit("refs/heads/#{@ref}")
- end
-
- render layout: false
- end
-
- def update_branches
- @target_project = selected_target_project
- @target_branches = @target_project.repository.branch_names
-
- render layout: false
- end
-
def assign_related_issues
- result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
+ result = ::MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
- respond_to do |format|
- format.html do
- case result[:count]
- when 0
- flash[:error] = "Failed to assign you issues related to the merge request"
- when 1
- flash[:notice] = "1 issue has been assigned to you"
- else
- flash[:notice] = "#{result[:count]} issues have been assigned to you"
- end
-
- redirect_to(merge_request_path(@merge_request))
- end
+ case result[:count]
+ when 0
+ flash[:error] = "Failed to assign you issues related to the merge request"
+ when 1
+ flash[:notice] = "1 issue has been assigned to you"
+ else
+ flash[:notice] = "#{result[:count]} issues have been assigned to you"
end
+
+ redirect_to(merge_request_path(@merge_request))
end
def pipeline_status
@@ -402,21 +211,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController
stop_url =
if environment.stop_action? && can?(current_user, :create_deployment, environment)
- stop_namespace_project_environment_path(project.namespace, project, environment)
+ stop_project_environment_path(project, environment)
end
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
- metrics_namespace_project_environment_deployment_path(environment.project.namespace,
- environment.project,
- environment,
- deployment)
+ metrics_project_environment_deployment_path(environment.project, environment, deployment)
end
{
id: environment.id,
name: environment.name,
- url: namespace_project_environment_path(project.namespace, project, environment),
+ url: project_environment_path(project, environment),
metrics_url: metrics_url,
stop_url: stop_url,
external_url: environment.external_url,
@@ -432,17 +238,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
protected
- def selected_target_project
- if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
- @project
- else
- @project.forked_project_link.forked_from_project
- end
- end
-
- def merge_request
- @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
- end
alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
@@ -455,12 +250,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
end
- def authorize_can_resolve_conflicts!
- @conflicts_list = MergeRequests::Conflicts::ListService.new(@merge_request)
-
- return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
- end
-
def validates_merge_request
# Show git not found page
# if there is no saved commits between source & target branch
@@ -470,133 +259,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- def define_show_vars
- @noteable = @merge_request
- @commits_count = @merge_request.commits_count
-
- if @merge_request.locked_long_ago?
- @merge_request.unlock_mr
- @merge_request.close
- end
-
- labels
- define_pipelines_vars
- end
-
- # Discussion tab data is rendered on html responses of actions
- # :show, :diff, :commits, :builds. but not when request the data through AJAX
- def define_discussion_vars
- # Build a note object for comment form
- @note = @project.notes.new(noteable: @merge_request)
-
- @discussions = @merge_request.discussions
- @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
- end
-
- def define_diff_vars
- @merge_request_diff =
- if params[:diff_id]
- @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
- else
- @merge_request.merge_request_diff
- end
-
- @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
- @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
-
- if params[:start_sha].present?
- @start_sha = params[:start_sha]
- @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
-
- unless @start_version
- @start_sha = @merge_request_diff.head_commit_sha
- @start_version = @merge_request_diff
- end
- end
-
- @compare =
- if @start_sha
- @merge_request_diff.compare_with(@start_sha)
- else
- @merge_request_diff
- end
-
- @diffs = @compare.diffs(diff_options)
- end
-
- def define_diff_comment_vars
- @new_diff_note_attrs = {
- noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id
- }
-
- @diff_notes_disabled = false
-
- @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
-
- @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
- @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
- end
-
- def define_pipelines_vars
- @pipelines = @merge_request.all_pipelines
- @pipeline = @merge_request.head_pipeline
- @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
- end
-
- def define_new_vars
- @noteable = @merge_request
-
- @target_branches = if @merge_request.target_project
- @merge_request.target_project.repository.branch_names
- else
- []
- end
-
- @target_project = merge_request.target_project
- @source_project = merge_request.source_project
- @commits = @merge_request.compare_commits.reverse
- @commit = @merge_request.diff_head_commit
-
- @note_counts = Note.where(commit_id: @commits.map(&:id))
- .group(:commit_id).count
-
- @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
-
- @show_changes_tab = params[:show_changes].present?
-
- define_pipelines_vars
- end
-
def invalid_mr
# Render special view for MR with removed target branch
render 'invalid'
end
- def merge_request_params
- params.require(:merge_request)
- .permit(merge_request_params_attributes)
- end
-
- def merge_request_params_attributes
- [
- :assignee_id,
- :description,
- :force_remove_source_branch,
- :lock_version,
- :milestone_id,
- :source_branch,
- :source_project_id,
- :state_event,
- :target_branch,
- :target_project_id,
- :task_num,
- :title,
-
- label_ids: []
- ]
- end
-
def merge_params
params.permit(merge_params_attributes)
end
@@ -605,22 +272,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
[:should_remove_source_branch, :commit_message]
end
- # Make sure merge requests created before 8.0
- # have head file in refs/merge-requests/
- def ensure_ref_fetched
- @merge_request.ensure_ref_fetched
- end
-
def merge_when_pipeline_succeeds_active?
params[:merge_when_pipeline_succeeds].present? &&
@merge_request.head_pipeline && @merge_request.head_pipeline.active?
end
- def build_merge_request
- params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
- @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
- end
-
def close_merge_request_without_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
@@ -648,7 +304,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return :failed unless @merge_request.head_pipeline
if @merge_request.head_pipeline.active?
- MergeRequests::MergeWhenPipelineSucceedsService
+ ::MergeRequests::MergeWhenPipelineSucceedsService
.new(@project, current_user, merge_params)
.execute(@merge_request)
@@ -672,4 +328,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def serializer
MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
end
+
+ def define_edit_vars
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+ @target_branches = @merge_request.target_project.repository.branch_names
+ end
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 953b1e83e49..c94384d2a1a 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -13,20 +13,16 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
- @milestones =
- case params[:state]
- when 'all' then @project.milestones
- when 'closed' then @project.milestones.closed
- else @project.milestones.active
- end
-
@sort = params[:sort] || 'due_date_asc'
- @milestones = @milestones.sort(@sort)
+ @milestones = milestones.sort(@sort)
respond_to do |format|
format.html do
@project_namespace = @project.namespace.becomes(Namespace)
- @milestones = @milestones.includes(:project)
+ # We need to show group milestones in the JSON response
+ # so that people can filter by and assign group milestones,
+ # but we don't need to show them on the project milestones page itself.
+ @milestones = @milestones.for_projects
@milestones = @milestones.page(params[:page])
end
format.json do
@@ -45,14 +41,14 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def show
+ @project_namespace = @project.namespace.becomes(Namespace)
end
def create
@milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute
- if @milestone.save
- redirect_to namespace_project_milestone_path(@project.namespace,
- @project, @milestone)
+ if @milestone.valid?
+ redirect_to project_milestone_path(@project, @milestone)
else
render "new"
end
@@ -65,8 +61,7 @@ class Projects::MilestonesController < Projects::ApplicationController
format.js
format.html do
if @milestone.valid?
- redirect_to namespace_project_milestone_path(@project.namespace,
- @project, @milestone)
+ redirect_to project_milestone_path(@project, @milestone)
else
render :edit
end
@@ -87,6 +82,18 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
+ def milestones
+ @milestones ||= begin
+ if @project.group && can?(current_user, :read_group, @project.group)
+ group = @project.group
+ end
+
+ search_params = params.merge(project_ids: @project.id, group_ids: group&.id)
+
+ MilestonesFinder.new(search_params).execute
+ end
+ end
+
def milestone
@milestone ||= @project.milestones.find_by!(iid: params[:id])
end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 33a152ad34f..dfa5e4f7f46 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit
def show
- @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
- @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
+ @url = project_network_path(@project, @ref, @options.merge(format: :json))
+ @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html do
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 28b383e69eb..d421b1a8eb5 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -15,7 +15,7 @@ class Projects::PagesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_pages_path(@project.namespace, @project),
+ redirect_to project_pages_path(@project),
status: 302,
notice: 'Pages were removed'
end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index dbd011f6c5d..15e77d854dc 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -16,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
@domain = @project.pages_domains.create(pages_domain_params)
if @domain.valid?
- redirect_to namespace_project_pages_path(@project.namespace, @project)
+ redirect_to project_pages_path(@project)
else
render 'new'
end
@@ -27,7 +27,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_pages_path(@project.namespace, @project),
+ redirect_to project_pages_path(@project),
status: 302,
notice: 'Domain was removed'
end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index ef4f083b98f..ec7c645df5a 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -1,10 +1,11 @@
class Projects::PipelineSchedulesController < Projects::ApplicationController
+ before_action :schedule, except: [:index, :new, :create]
+
before_action :authorize_read_pipeline_schedule!
- before_action :authorize_create_pipeline_schedule!, only: [:new, :create, :edit, :take_ownership, :update]
+ before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
+ before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
- before_action :schedule, only: [:edit, :update, :destroy, :take_ownership]
-
def index
@scope = params[:scope]
@all_schedules = PipelineSchedulesFinder.new(@project).execute
@@ -33,7 +34,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def update
if schedule.update(schedule_params)
- redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project)
+ redirect_to project_pipeline_schedules_path(@project)
else
render :edit
end
@@ -52,7 +53,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
redirect_to pipeline_schedules_path(@project), status: 302
else
redirect_to pipeline_schedules_path(@project),
- status: 302,
+ status: :forbidden,
alert: _("Failed to remove the pipeline schedule")
end
end
@@ -65,6 +66,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def schedule_params
params.require(:schedule)
- .permit(:description, :cron, :cron_timezone, :ref, :active)
+ .permit(:description, :cron, :cron_timezone, :ref, :active,
+ variables_attributes: [:id, :key, :value, :_destroy] )
+ end
+
+ def authorize_update_pipeline_schedule!
+ return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
+ end
+
+ def authorize_admin_pipeline_schedule!
+ return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 303e91a8dc0..a3bfbf0694e 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -60,7 +60,7 @@ class Projects::PipelinesController < Projects::ApplicationController
.execute(:web, ignore_skip_ci: true, save_on_errors: false)
if @pipeline.persisted?
- redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
+ redirect_to project_pipeline_path(project, @pipeline)
else
render 'new'
end
@@ -111,7 +111,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }
@@ -123,7 +123,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 38a47651000..9d24ebe2138 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
+ redirect_to project_settings_ci_cd_path(@project, params: params)
end
def update
if @project.update_attributes(update_params)
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
- :public_builds, :auto_cancel_pending_pipelines
+ :public_builds, :auto_cancel_pending_pipelines, :ci_config_path
)
end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d2d26738582..f8ff7413b53 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -6,8 +6,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
def index
- sort = params[:sort].presence || sort_value_name
- redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort)
+ @sort = params[:sort].presence || sort_value_name
+ @group_links = @project.project_group_links
+
+ @skip_groups = @group_links.pluck(:group_id)
+ @skip_groups << @project.namespace_id unless @project.personal?
+ @skip_groups += @project.group.ancestors.pluck(:id) if @project.group
+
+ @project_members = MembersFinder.new(@project, current_user).execute
+
+ if params[:search].present?
+ @project_members = @project_members.joins(:user).merge(User.search(params[:search]))
+ @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
+ end
+
+ @project_members = @project_members.sort(@sort).page(params[:page])
+ @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_member = @project.project_members.new
end
def update
@@ -19,7 +34,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def resend_invite
- redirect_path = namespace_project_settings_members_path(@project.namespace, @project)
+ redirect_path = project_project_members_path(@project)
@project_member = @project.project_members.find(params[:id])
@@ -42,7 +57,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
return render_404
end
- redirect_to(namespace_project_settings_members_path(project.namespace, project),
+ redirect_to(project_project_members_path(project),
notice: notice)
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 2a0b58fae7c..1eb78d8b522 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController
new_path =
case params[:destination]
when "tree"
- namespace_project_tree_path(@project.namespace, @project, @id)
+ project_tree_path(@project, @id)
when "blob"
- namespace_project_blob_path(@project.namespace, @project, @id)
+ project_blob_path(@project, @id)
when "graph"
- namespace_project_network_path(@project.namespace, @project, @id, @options)
+ project_network_path(@project, @id, @options)
when "graphs"
- namespace_project_graph_path(@project.namespace, @project, @id)
+ project_graph_path(@project, @id)
when "find_file"
- namespace_project_find_file_path(@project.namespace, @project, @id)
+ project_find_file_path(@project, @id)
when "graphs_commits"
- commits_namespace_project_graph_path(@project.namespace, @project, @id)
+ commits_project_graph_path(@project, @id)
when "badges"
- namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
+ project_pipelines_settings_path(@project, ref: @id)
else
- namespace_project_commits_path(@project.namespace, @project, @id)
+ project_commits_path(@project, @id)
end
redirect_to new_path
@@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController
offset = (@offset + @limit)
if contents.size > offset
- @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
+ @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset)
end
respond_to do |format|
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 98e78585be8..71e7dc70a4d 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -10,11 +10,11 @@ module Projects
def destroy
if image.destroy
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Image repository has been removed successfully!'
else
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove image repository!'
end
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index 5050dba3aab..ae72bd03cfb 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -5,11 +5,11 @@ module Projects
def destroy
if tag.delete
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Registry tag has been removed successfully!'
else
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove registry tag!'
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 2c097cb4d8d..3e0a530fdb9 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController
release.destroy
end
- redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+ redirect_to project_tag_path(@project, @tag.name)
end
private
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 160e632648a..9f9773575a5 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def edit
@@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
protected
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 704f8cc8a79..d54a1111f11 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -15,7 +15,7 @@ class Projects::ServicesController < Projects::ApplicationController
def update
if @service.save(context: :manual_change)
- redirect_to(namespace_project_settings_integrations_path(@project.namespace, @project), notice: success_message)
+ redirect_to(project_settings_integrations_path(@project), notice: success_message)
else
render 'edit'
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 24fe78bc1bd..ea7ceb3eaa5 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -21,7 +21,10 @@ module Projects
end
def define_secret_variables
- @variable = Ci::Variable.new
+ @variable = Ci::Variable.new(project: project)
+ .present(current_user: current_user)
+ @variables = project.variables.order_key_asc
+ .map { |variable| variable.present(current_user: current_user) }
end
def define_triggers_variables
diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb
deleted file mode 100644
index 54f9dceddef..00000000000
--- a/app/controllers/projects/settings/members_controller.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Projects
- module Settings
- class MembersController < Projects::ApplicationController
- include SortingHelper
-
- def show
- @sort = params[:sort].presence || sort_value_name
- @group_links = @project.project_group_links
-
- @skip_groups = @group_links.pluck(:group_id)
- @skip_groups << @project.namespace_id unless @project.personal?
- @skip_groups += @project.group.ancestors.pluck(:id) if @project.group
-
- @project_members = MembersFinder.new(@project, current_user).execute
-
- if params[:search].present?
- @project_members = @project_members.joins(:user).merge(User.search(params[:search]))
- @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
- end
-
- @project_members = @project_members.sort(@sort).page(params[:page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
- @project_member = @project.project_members.new
- end
- end
- end
-end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 98dd307bd9d..d07143d294f 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -30,7 +30,7 @@ class Projects::SnippetsController < Projects::ApplicationController
).execute
@snippets = @snippets.page(params[:page])
if @snippets.out_of_range? && @snippets.total_pages != 0
- redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
+ redirect_to project_snippets_path(@project, page: @snippets.total_pages)
end
end
@@ -79,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.destroy
- redirect_to namespace_project_snippets_path(@project.namespace, @project), status: 302
+ redirect_to project_snippets_path(@project), status: 302
end
protected
@@ -90,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
+ def spammable_path
+ project_snippet_path(@project, @snippet)
+ end
+
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index ebc9f4edab4..b62d7d9b7c5 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -35,7 +35,7 @@ class Projects::TagsController < Projects::ApplicationController
if result[:status] == :success
@tag = result[:tag]
- redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+ redirect_to project_tag_path(@project, @tag.name)
else
@error = result[:message]
@message = params[:message]
@@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format|
if result[:status] == :success
format.html do
- redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303
+ redirect_to project_tags_path(@project), status: 303
end
format.js
@@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController
@error = result[:message]
format.html do
- redirect_to namespace_project_tags_path(@project.namespace, @project),
+ redirect_to project_tags_path(@project),
alert: @error, status: 303
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 266a15c1cf9..30181ac3bdf 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
return redirect_to(
- namespace_project_blob_path(@project.namespace, @project,
+ project_blob_path(@project,
File.join(@ref, @path))
)
elsif @path.present?
@@ -37,8 +37,8 @@ class Projects::TreeController < Projects::ApplicationController
return render_404 unless @commit_params.values.all?
create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
- success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
- failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
+ success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
+ failure_path: project_tree_path(@project, @ref))
end
private
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index e86adddd77f..a5b17fa65ea 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def create
@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.'
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def take_ownership
@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.'
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def edit
@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger."
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), status: 302
+ redirect_to project_settings_ci_cd_path(@project), status: 302
end
private
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 50e25a00f03..6a825137564 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,50 +1,60 @@
class Projects::VariablesController < Projects::ApplicationController
+ before_action :variable, only: [:show, :update, :destroy]
before_action :authorize_admin_build!
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def show
- @variable = @project.variables.find(params[:id])
end
def update
- @variable = @project.variables.find(params[:id])
-
- if @variable.update_attributes(project_params)
- redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
+ if variable.update(variable_params)
+ redirect_to project_variables_path(project),
+ notice: 'Variable was successfully updated.'
else
- render action: "show"
+ render "show"
end
end
def create
- @variable = Ci::Variable.new(project_params)
+ @variable = project.variables.create(variable_params)
+ .present(current_user: current_user)
- if @variable.valid? && @project.variables << @variable
- flash[:notice] = 'Variables were successfully updated.'
- redirect_to namespace_project_settings_ci_cd_path(project.namespace, project)
+ if @variable.persisted?
+ redirect_to project_settings_ci_cd_path(project),
+ notice: 'Variable was successfully created.'
else
render "show"
end
end
def destroy
- @key = @project.variables.find(params[:id])
- @key.destroy
-
- redirect_to namespace_project_settings_ci_cd_path(project.namespace, project),
- status: 302,
- notice: 'Variable was successfully removed.'
+ if variable.destroy
+ redirect_to project_settings_ci_cd_path(project),
+ status: 302,
+ notice: 'Variable was successfully removed.'
+ else
+ redirect_to project_settings_ci_cd_path(project),
+ status: 302,
+ notice: 'Failed to remove the variable.'
+ end
end
private
- def project_params
- params.require(:variable)
- .permit([:id, :key, :value, :protected, :_destroy])
+ def variable_params
+ params.require(:variable).permit(*variable_params_attributes)
+ end
+
+ def variable_params_attributes
+ %i[id key value protected _destroy]
+ end
+
+ def variable
+ @variable ||= project.variables.find(params[:id]).present(current_user: current_user)
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index e54b90b8d52..ac98470c2b1 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, @page),
+ project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@@ -62,7 +62,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, @page),
+ project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@@ -75,7 +75,7 @@ class Projects::WikisController < Projects::ApplicationController
unless @page
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, :home),
+ project_wiki_path(@project, :home),
notice: "Page not found"
)
end
@@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
WikiPages::DestroyService.new(@project, current_user).execute(@page)
- redirect_to namespace_project_wiki_path(@project.namespace, @project, :home),
+ redirect_to project_wiki_path(@project, :home),
status: 302,
notice: "Page was successfully deleted"
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 5480814874b..87a69e8e6f9 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -92,12 +92,12 @@ class ProjectsController < Projects::ApplicationController
def show
if @project.import_in_progress?
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
return
end
if @project.pending_delete?
- flash[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
+ flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
end
respond_to do |format|
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 4a579601785..d58c8d14a75 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
query = params[:search].strip.downcase
found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
- redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
+ redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha
end
end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 3d86dd2ea2c..8c3abd0a085 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -107,6 +107,10 @@ class SnippetsController < ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
+ def spammable_path
+ snippet_path(@snippet)
+ end
+
def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/finders/concerns/created_at_filter.rb b/app/finders/concerns/created_at_filter.rb
new file mode 100644
index 00000000000..ac9ac77732c
--- /dev/null
+++ b/app/finders/concerns/created_at_filter.rb
@@ -0,0 +1,8 @@
+module CreatedAtFilter
+ def by_created_at(items)
+ items = items.created_before(params[:created_before]) if params[:created_before].present?
+ items = items.created_after(params[:created_after]) if params[:created_after].present?
+
+ items
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 558f8b5e2e5..2e5a6493134 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,7 +19,10 @@
# iids: integer[]
#
class IssuableFinder
+ include CreatedAtFilter
+
NONE = '0'.freeze
+ IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
attr_accessor :current_user, :params
@@ -31,6 +34,7 @@ class IssuableFinder
def execute
items = init_collection
items = by_scope(items)
+ items = by_created_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
@@ -41,7 +45,6 @@ class IssuableFinder
items = by_iids(items)
items = by_milestone(items)
items = by_label(items)
- items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items)
@@ -62,7 +65,7 @@ class IssuableFinder
# grouping and counting within that query.
#
def count_by_state
- count_params = params.merge(state: nil, sort: nil)
+ count_params = params.merge(state: nil, sort: nil, for_counting: true)
labels_count = label_names.any? ? label_names.count : 1
finder = self.class.new(current_user, count_params)
counts = Hash.new(0)
@@ -86,6 +89,10 @@ class IssuableFinder
execute.find_by!(*params)
end
+ def state_counter_cache_key(state)
+ Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-'))
+ end
+
def group
return @group if defined?(@group)
@@ -142,9 +149,17 @@ class IssuableFinder
@milestones =
if milestones?
- scope = Milestone.where(project_id: projects)
+ if project?
+ group_id = project.group&.id
+ project_id = project.id
+ end
+
+ group_id = group.id if group
- scope.where(title: params[:milestone_title])
+ search_params =
+ { title: params[:milestone_title], project_ids: project_id, group_ids: group_id }
+
+ MilestonesFinder.new(search_params).execute
else
Milestone.none
end
@@ -326,11 +341,6 @@ class IssuableFinder
items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
else
items = items.with_milestone(params[:milestone_title])
- items_projects = projects(items)
-
- if items_projects
- items = items.where(milestones: { project_id: items_projects })
- end
end
end
@@ -403,19 +413,16 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items
end
- def by_created_at(items)
- if params[:created_after].present?
- items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
- end
-
- if params[:created_before].present?
- items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
- end
-
- items
- end
-
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
+
+ def state_counter_cache_key_components(state)
+ opts = params.with_indifferent_access
+ opts[:state] = state
+ opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+ opts.delete_if { |_, value| value.blank? }
+
+ ['issuables_count', klass.to_ability_name, opts.sort]
+ end
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 3da5508aefd..85230ff1293 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -16,14 +16,72 @@
# sort: string
#
class IssuesFinder < IssuableFinder
+ CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
+
def klass
Issue
end
+ def with_confidentiality_access_check
+ return Issue.all if user_can_see_all_confidential_issues?
+ return Issue.where('issues.confidential IS NOT TRUE') if user_cannot_see_confidential_issues?
+
+ Issue.where('
+ issues.confidential IS NOT TRUE
+ OR (issues.confidential = TRUE
+ AND (issues.author_id = :user_id
+ OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
+ OR issues.project_id IN(:project_ids)))',
+ user_id: current_user.id,
+ project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id))
+ end
+
private
def init_collection
- IssuesFinder.not_restricted_by_confidentiality(current_user)
+ with_confidentiality_access_check
+ end
+
+ def user_can_see_all_confidential_issues?
+ return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
+
+ return @user_can_see_all_confidential_issues = false if current_user.blank?
+ return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
+
+ @user_can_see_all_confidential_issues =
+ project? &&
+ project &&
+ project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
+ end
+
+ # Anonymous users can't see any confidential issues.
+ #
+ # Users without access to see _all_ confidential issues (as in
+ # `user_can_see_all_confidential_issues?`) are more complicated, because they
+ # can see confidential issues where:
+ # 1. They are an assignee.
+ # 2. They are an author.
+ #
+ # That's fine for most cases, but if we're just counting, we need to cache
+ # effectively. If we cached this accurately, we'd have a cache key for every
+ # authenticated user without sufficient access to the project. Instead, when
+ # we are counting, we treat them as if they can't see any confidential issues.
+ #
+ # This does mean the counts may be wrong for those users, but avoids an
+ # explosion in cache keys.
+ def user_cannot_see_confidential_issues?(for_counting: false)
+ return false if user_can_see_all_confidential_issues?
+
+ current_user.blank? || for_counting || params[:for_counting]
+ end
+
+ def state_counter_cache_key_components(state)
+ extra_components = [
+ user_can_see_all_confidential_issues?,
+ user_cannot_see_confidential_issues?(for_counting: true)
+ ]
+
+ super + extra_components
end
def by_assignee(items)
@@ -38,21 +96,6 @@ class IssuesFinder < IssuableFinder
end
end
- def self.not_restricted_by_confidentiality(user)
- return Issue.where('issues.confidential IS NOT TRUE') if user.blank?
-
- return Issue.all if user.full_private_access?
-
- Issue.where('
- issues.confidential IS NOT TRUE
- OR (issues.confidential = TRUE
- AND (issues.author_id = :user_id
- OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
- OR issues.project_id IN(:project_ids)))',
- user_id: user.id,
- project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
- end
-
def item_project_ids(items)
items&.reorder(nil)&.select(:project_id)
end
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 042d792dada..ce432ddbfe6 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
def projects
return @projects if defined?(@projects)
- @projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute
+ @projects = if skip_authorization
+ Project.all
+ else
+ ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
+ end
+
@projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil)
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 630c73c2a94..0a5a0ea2f35 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,12 +1,56 @@
+# Search for milestones
+#
+# params - Hash
+# project_ids: Array of project ids or single project id.
+# group_ids: Array of group ids or single group id.
+# order - Orders by field default due date asc.
+# title - filter by title.
+# state - filters by state.
+
class MilestonesFinder
- def execute(projects, params)
- milestones = Milestone.of_projects(projects)
- milestones = milestones.reorder("due_date ASC")
-
- case params[:state]
- when 'closed' then milestones.closed
- when 'all' then milestones
- else milestones.active
+ attr_reader :params, :project_ids, :group_ids
+
+ def initialize(params = {})
+ @project_ids = Array(params[:project_ids])
+ @group_ids = Array(params[:group_ids])
+ @params = params
+ end
+
+ def execute
+ return Milestone.none if project_ids.empty? && group_ids.empty?
+
+ items = Milestone.all
+ items = by_groups_and_projects(items)
+ items = by_title(items)
+ items = by_state(items)
+
+ order(items)
+ end
+
+ private
+
+ def by_groups_and_projects(items)
+ items.for_projects_and_groups(project_ids, group_ids)
+ end
+
+ def by_title(items)
+ if params[:title]
+ items.where(title: params[:title])
+ else
+ items
+ end
+ end
+
+ def by_state(items)
+ Milestone.filter_by_state(items, params[:state])
+ end
+
+ def order(items)
+ if params.has_key?(:order)
+ items.reorder(params[:order])
+ else
+ order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
+ items.reorder(order_statement)
end
end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 8bfbe37c543..aa80dfc3f37 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder
end
def execute
- collection = init_collection
+ user = params.delete(:user)
+ collection =
+ if user
+ PersonalProjectsFinder.new(user).execute(current_user)
+ else
+ init_collection
+ end
+
collection = by_ids(collection)
collection = by_personal(collection)
collection = by_starred(collection)
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index c358f23f541..3fe37c75381 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -83,6 +83,8 @@ class TodosFinder
if project?
@project = Project.find(params[:project_id])
+ @project = nil if @project.pending_delete?
+
unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index dbd50d1db7c..33f7ae90598 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -14,6 +14,8 @@
# external: boolean
#
class UsersFinder
+ include CreatedAtFilter
+
attr_accessor :current_user, :params
def initialize(current_user, params = {})
@@ -29,6 +31,7 @@ class UsersFinder
users = by_active(users)
users = by_external_identity(users)
users = by_external(users)
+ users = by_created_at(users)
users
end
@@ -60,13 +63,13 @@ class UsersFinder
end
def by_external_identity(users)
- return users unless current_user.admin? && params[:extern_uid] && params[:provider]
+ return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end
def by_external(users)
- return users = users.where.not(external: true) unless current_user.admin?
+ return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external]
users.external
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index dc7ff78f3df..1c165700b19 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -131,10 +131,7 @@ module ApplicationHelper
end
def body_data_page
- path = controller.controller_path.split('/')
- namespace = path.first if path.second
-
- [namespace, controller.controller_name, controller.action_name].compact.join(':')
+ [*controller.controller_path.split('/'), controller.action_name].compact.join(':')
end
# shortcut for gitlab config
@@ -301,10 +298,6 @@ module ApplicationHelper
end
end
- def can_toggle_new_nav?
- Rails.env.development?
- end
-
def show_new_nav?
cookies["new_nav"] == "true"
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index ca326dd0627..f652f4901b7 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -34,17 +34,17 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
- def restricted_level_checkboxes(help_block_id)
+ def restricted_level_checkboxes(help_block_id, checkbox_name)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : ''
- checkbox_name = "application_setting[restricted_visibility_levels][]"
+ tag_name = "application_setting_visibility_level_#{level}"
- label_tag(name, class: css_class) do
+ label_tag(tag_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id,
- id: name) + visibility_level_icon(level) + name
+ id: tag_name) + visibility_level_icon(level) + name
end
end
end
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 024cf38469e..86b19368cfd 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -7,7 +7,7 @@ module AwardEmojiHelper
if awardable.for_personal_snippet?
toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
else
- toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
+ toggle_award_emoji_project_note_path(@project, awardable.id)
end
else
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 3efa7c36057..e964d7a5e16 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -9,7 +9,7 @@ module BlobHelper
end
def edit_path(project = @project, ref = @ref, path = @path, options = {})
- namespace_project_edit_blob_path(project.namespace, project,
+ project_edit_blob_path(project,
tree_join(ref, path),
options[:link_opts])
end
@@ -33,7 +33,7 @@ module BlobHelper
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag 'Edit',
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -62,7 +62,7 @@ module BlobHelper
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag label,
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -120,15 +120,15 @@ module BlobHelper
def blob_raw_url
if @build && @entry
- raw_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: @entry.path)
+ raw_project_job_artifacts_path(@project, @build, path: @entry.path)
elsif @snippet
if @snippet.project_id
- raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
+ raw_project_snippet_path(@project, @snippet)
else
raw_snippet_path(@snippet)
end
elsif @blob
- namespace_project_raw_path(@project.namespace, @project, @id)
+ project_raw_path(@project, @id)
end
end
@@ -279,12 +279,12 @@ module BlobHelper
options = []
if can?(current_user, :create_issue, project)
- options << link_to("submit an issue", new_namespace_project_issue_path(project.namespace, project))
+ options << link_to("submit an issue", new_project_issue_path(project))
end
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
if merge_project
- options << link_to("create a merge request", new_namespace_project_merge_request_path(project.namespace, project))
+ options << link_to("create a merge request", project_new_merge_request_path(project))
end
options
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index e2df52e3833..8b33c362a9c 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -3,12 +3,12 @@ module BoardsHelper
board = @board || @boards.first
{
- endpoint: namespace_project_boards_path(@project.namespace, @project),
+ endpoint: project_boards_path(@project),
board_id: board.id,
disabled: "#{!can?(current_user, :admin_list, @project)}",
- issue_link_base: namespace_project_issues_path(@project.namespace, @project),
+ issue_link_base: project_issues_path(@project),
root_path: root_path,
- bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
+ bulk_update_path: bulk_update_project_issues_path(@project),
default_avatar: image_path(default_avatar)
}
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 59519c1335b..686437fc99a 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -7,7 +7,7 @@ module BranchesHelper
options = exist_opts.merge(options)
- namespace_project_branches_path(@project.namespace, @project, @id, options)
+ project_branches_path(@project, @id, options)
end
def can_push_branch?(project, branch_name)
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index f0a0d245dc0..85bc784d53c 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,8 @@ module BuildsHelper
def javascript_build_options
{
- page_url: namespace_project_job_url(@project.namespace, @project, @build),
- build_url: namespace_project_job_url(@project.namespace, @project, @build, :json),
+ page_url: project_job_url(@project, @build),
+ build_url: project_job_url(@project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
@@ -31,7 +31,7 @@ module BuildsHelper
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
- description: namespace_project_job_url(@project.namespace, @project, @build)
+ description: project_job_url(@project, @build)
}
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 21c0eb8b54c..8022547a6ad 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -8,7 +8,7 @@
module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
- namespace_project_pipeline_path(project.namespace, project, pipeline)
+ project_pipeline_path(project, pipeline)
end
def ci_label_for_status(status)
@@ -99,10 +99,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- pipeline_status.sha)
+ path = pipelines_project_commit_path(project, pipeline_status.sha)
render_status_with_link(
'commit',
@@ -113,10 +110,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- commit)
+ path = pipelines_project_commit_path(project, commit)
render_status_with_link(
'commit',
@@ -127,7 +121,7 @@ module CiStatusHelper
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
- path = namespace_project_pipeline_path(project.namespace, project, pipeline)
+ path = project_pipeline_path(project, pipeline)
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 0accd1f8d77..d08e346d605 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -30,7 +30,7 @@ module CommitsHelper
crumbs = content_tag(:li) do
link_to(
@project.path,
- namespace_project_commits_path(@project.namespace, @project, @ref)
+ project_commits_path(@project, @ref)
)
end
@@ -42,8 +42,7 @@ module CommitsHelper
# The text is just the individual part, but the link needs all the parts before it
link_to(
part,
- namespace_project_commits_path(
- @project.namespace,
+ project_commits_path(
@project,
tree_join(@ref, parts[0..i].join('/'))
)
@@ -86,20 +85,20 @@ module CommitsHelper
if @path.blank?
return link_to(
_("Browse Files"),
- namespace_project_tree_path(project.namespace, project, commit),
+ project_tree_path(project, commit),
class: "btn btn-default"
)
elsif @repo.blob_at(commit.id, @path)
return link_to(
_("Browse File"),
- namespace_project_blob_path(project.namespace, project,
+ project_blob_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
elsif @path.present?
return link_to(
_("Browse Directory"),
- namespace_project_tree_path(project.namespace, project,
+ project_tree_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
@@ -165,7 +164,7 @@ module CommitsHelper
notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(@project.namespace, @project,
+ fork_path = project_forks_path(@project,
namespace_key: current_user.namespace.id,
continue: continue_params)
@@ -175,7 +174,7 @@ module CommitsHelper
def view_file_button(commit_sha, diff_new_path, project)
link_to(
- namespace_project_blob_path(project.namespace, project,
+ project_blob_path(project,
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 2aa0449c46e..2c28dd81c87 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -9,8 +9,7 @@ module CompareHelper
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
- new_namespace_project_merge_request_path(
- project.namespace,
+ project_new_merge_request_path(
project,
merge_request: {
source_branch: to,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 16a99addd0b..926502bf239 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -103,18 +103,18 @@ module DiffHelper
end
def diff_file_blob_raw_path(diff_file)
- namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+ project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
end
def diff_file_old_blob_raw_path(diff_file)
sha = diff_file.old_content_sha
return unless sha
- namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path))
+ project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
- blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+ blob_diff_path: project_blob_diff_path(project,
tree_join(diff_commit_id, diff_file_path)),
view: diff_view
}
@@ -142,7 +142,7 @@ module DiffHelper
diff_file = viewer.diff_file
options = []
- blob_url = namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+ blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
options << link_to('view the blob', blob_url)
options
@@ -163,17 +163,17 @@ module DiffHelper
end
def commit_diff_whitespace_link(project, commit, options)
- url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+ url = project_commit_path(project, commit.id, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_merge_request_whitespace_link(project, merge_request, options)
- url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+ url = diffs_project_merge_request_path(project, merge_request, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_compare_whitespace_link(project, from, to, options)
- url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
+ url = project_compare_path(project, from, to, params_with_whitespace)
toggle_whitespace_link(url, options)
end
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index ff8550439d0..1e78a189c08 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -8,7 +8,7 @@ module EnvironmentHelper
def environment_link_for_build(project, build)
environment = environment_for_build(project, build)
if environment
- link_to environment.name, namespace_project_environment_path(project.namespace, project, environment)
+ link_to environment.name, project_environment_path(project, environment)
else
content_tag :span, build.expanded_environment_name
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 515e802e01e..4ce89f89fa9 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -1,7 +1,7 @@
module EnvironmentsHelper
def environments_list_data
{
- endpoint: namespace_project_environments_path(@project.namespace, @project, format: :json)
+ endpoint: project_environments_path(@project, format: :json)
}
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 751d61955b7..48c87dca217 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -99,13 +99,12 @@ module EventsHelper
def event_feed_url(event)
if event.issue?
- namespace_project_issue_url(event.project.namespace, event.project,
+ project_issue_url(event.project,
event.issue)
elsif event.merge_request?
- namespace_project_merge_request_url(event.project.namespace,
- event.project, event.merge_request)
+ project_merge_request_url(event.project, event.merge_request)
elsif event.commit_note?
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
event.note_target)
elsif event.note?
if event.note_target
@@ -119,15 +118,15 @@ module EventsHelper
def push_event_feed_url(event)
if event.push_with_commits? && event.md_ref?
if event.commits_count > 1
- namespace_project_compare_url(event.project.namespace, event.project,
+ project_compare_url(event.project,
from: event.commit_from, to:
event.commit_to)
else
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
id: event.commit_to)
end
else
- namespace_project_commits_url(event.project.namespace, event.project,
+ project_commits_url(event.project,
event.ref_name)
end
end
@@ -146,15 +145,9 @@ module EventsHelper
def event_note_target_path(event)
if event.commit_note?
- namespace_project_commit_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_commit_path(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.project_snippet_note?
- namespace_project_snippet_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_snippet_path(event.project, event.note_target, anchor: dom_id(event.target))
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index defd87d6bbe..8cf890b74a8 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -4,7 +4,7 @@ module ExternalWikiHelper
if external_wiki_service
external_wiki_service.properties['external_wiki_url']
else
- namespace_project_wiki_path(project.namespace, project, :home)
+ project_wiki_path(project, :home)
end
end
end
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 8ceb5c36bda..9247b1f72de 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -16,8 +16,8 @@ module FormHelper
end
end
- def issue_dropdown_options(issuable, has_multiple_assignees = true)
- options = {
+ def issue_assignees_dropdown_options
+ {
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee',
filter: true,
@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username,
null_user: true,
current_user: true,
- project_id: issuable.project.try(:id),
- field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
+ project_id: @project.id,
+ field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned',
'max-select': 1,
'dropdown-header': 'Assignee',
@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name])
}
}
-
- if has_multiple_assignees
- options[:title] = 'Select assignee(s)'
- options[:data][:'dropdown-header'] = 'Assignee(s)'
- options[:data].delete(:'max-select')
- end
-
- options
end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 8c7af62e199..0517a699ae0 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -1,144 +1,93 @@
-# Shorter routing method for project and project items
-# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
-# so we use nested routing for project resources which include project and
-# project namespace. To avoid writing long methods every time we define shortcuts for
-# some of routing.
-#
-# For example instead of this:
-#
-# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
-#
-# We can simply use shortcut:
-#
-# merge_request_path(merge_request)
-#
+# Shorter routing method for some project items
module GitlabRoutingHelper
- # Project
- def project_path(project, *args)
- namespace_project_path(project.namespace, project, *args)
- end
-
- def project_url(project, *args)
- namespace_project_url(project.namespace, project, *args)
- end
-
- def edit_project_path(project, *args)
- edit_namespace_project_path(project.namespace, project, *args)
- end
-
- def edit_project_url(project, *args)
- edit_namespace_project_url(project.namespace, project, *args)
- end
+ extend ActiveSupport::Concern
- def project_files_path(project, *args)
- namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref)
+ included do
+ Gitlab::Routing.includes_helpers(self)
end
- def project_commits_path(project, *args)
- namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
- end
-
- def project_pipelines_path(project, *args)
- namespace_project_pipelines_path(project.namespace, project, *args)
- end
-
- def project_environments_path(project, *args)
- namespace_project_environments_path(project.namespace, project, *args)
- end
-
- def project_cycle_analytics_path(project, *args)
- namespace_project_cycle_analytics_path(project.namespace, project, *args)
+ # Project
+ def project_tree_path(project, ref = nil, *args)
+ namespace_project_tree_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
- def project_jobs_path(project, *args)
- namespace_project_jobs_path(project.namespace, project, *args)
+ def project_commits_path(project, ref = nil, *args)
+ namespace_project_commits_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
def project_ref_path(project, ref_name, *args)
- namespace_project_commits_path(project.namespace, project, ref_name, *args)
- end
-
- def project_container_registry_path(project, *args)
- namespace_project_container_registry_index_path(project.namespace, project, *args)
- end
-
- def activity_project_path(project, *args)
- activity_namespace_project_path(project.namespace, project, *args)
+ project_commits_path(project, ref_name, *args)
end
def runners_path(project, *args)
- namespace_project_runners_path(project.namespace, project, *args)
+ project_runners_path(project, *args)
end
def runner_path(runner, *args)
- namespace_project_runner_path(@project.namespace, @project, runner, *args)
+ project_runner_path(@project, runner, *args)
end
def environment_path(environment, *args)
- namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ project_environment_path(environment.project, environment, *args)
end
def environment_metrics_path(environment, *args)
- metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ metrics_project_environment_path(environment.project, environment, *args)
end
def issue_path(entity, *args)
- namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
+ project_issue_path(entity.project, entity, *args)
end
def merge_request_path(entity, *args)
- namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_path(entity.project, entity, *args)
end
def pipeline_path(pipeline, *args)
- namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+ project_pipeline_path(pipeline.project, pipeline.id, *args)
end
def milestone_path(entity, *args)
- namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
+ project_milestone_path(entity.project, entity, *args)
end
def issue_url(entity, *args)
- namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
+ project_issue_url(entity.project, entity, *args)
end
def merge_request_url(entity, *args)
- namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_url(entity.project, entity, *args)
end
def pipeline_url(pipeline, *args)
- namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+ project_pipeline_url(pipeline.project, pipeline.id, *args)
end
def pipeline_job_url(pipeline, build, *args)
- namespace_project_job_url(pipeline.project.namespace, pipeline.project, build.id, *args)
+ project_job_url(pipeline.project, build.id, *args)
end
def commits_url(entity, *args)
- namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args)
+ project_commits_url(entity.project, entity.ref, *args)
end
def commit_url(entity, *args)
- namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args)
- end
-
- def project_snippet_url(entity, *args)
- namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+ project_commit_url(entity.project, entity.sha, *args)
end
def preview_markdown_path(project, *args)
if @snippet.is_a?(PersonalSnippet)
preview_markdown_snippets_path
else
- preview_markdown_namespace_project_path(project.namespace, project, *args)
+ preview_markdown_project_path(project, *args)
end
end
def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue)
- toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_issue_path(entity.project, entity)
else
- toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_merge_request_path(entity.project, entity)
end
end
@@ -152,32 +101,27 @@ module GitlabRoutingHelper
## Members
def project_members_url(project, *args)
- namespace_project_project_members_url(project.namespace, project)
+ project_project_members_url(project, *args)
end
def project_member_path(project_member, *args)
- namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ project_project_member_path(project_member.source, project_member)
end
def request_access_project_members_path(project, *args)
- request_access_namespace_project_project_members_path(project.namespace, project)
+ request_access_project_project_members_path(project)
end
def leave_project_members_path(project, *args)
- leave_namespace_project_project_members_path(project.namespace, project)
+ leave_project_project_members_path(project)
end
def approve_access_request_project_member_path(project_member, *args)
- approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ approve_access_request_project_project_member_path(project_member.source, project_member)
end
def resend_invite_project_member_path(project_member, *args)
- resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
- end
-
- # Snippets
- def personal_snippet_url(snippet, *args)
- snippet_url(snippet)
+ resend_invite_project_project_member_path(project_member.source, project_member)
end
# Groups
@@ -211,50 +155,37 @@ module GitlabRoutingHelper
def artifacts_action_path(path, project, build)
action, path_params = path.split('/', 2)
- args = [project.namespace, project, build, path_params]
+ args = [project, build, path_params]
case action
when 'download'
- download_namespace_project_job_artifacts_path(*args)
+ download_project_job_artifacts_path(*args)
when 'browse'
- browse_namespace_project_job_artifacts_path(*args)
+ browse_project_job_artifacts_path(*args)
when 'file'
- file_namespace_project_job_artifacts_path(*args)
+ file_project_job_artifacts_path(*args)
when 'raw'
- raw_namespace_project_job_artifacts_path(*args)
+ raw_project_job_artifacts_path(*args)
end
end
# Pipeline Schedules
def pipeline_schedules_path(project, *args)
- namespace_project_pipeline_schedules_path(project.namespace, project, *args)
+ project_pipeline_schedules_path(project, *args)
end
def pipeline_schedule_path(schedule, *args)
project = schedule.project
- namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
+ project_pipeline_schedule_path(project, schedule, *args)
end
def edit_pipeline_schedule_path(schedule)
project = schedule.project
- edit_namespace_project_pipeline_schedule_path(project.namespace, project, schedule)
+ edit_project_pipeline_schedule_path(project, schedule)
end
def take_ownership_pipeline_schedule_path(schedule, *args)
project = schedule.project
- take_ownership_namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
- end
-
- # Settings
- def project_settings_integrations_path(project, *args)
- namespace_project_settings_integrations_path(project.namespace, project, *args)
- end
-
- def project_settings_members_path(project, *args)
- namespace_project_settings_members_path(project.namespace, project, *args)
- end
-
- def project_settings_ci_cd_path(project, *args)
- namespace_project_settings_ci_cd_path(project.namespace, project, *args)
+ take_ownership_project_pipeline_schedule_path(project, schedule, *args)
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index eb45241615f..8cd61f738e1 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -16,11 +16,12 @@ module GroupsHelper
full_title = ''
group.ancestors.reverse.each do |parent|
- full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable')
+ full_title += group_title_link(parent, hidable: true)
+
full_title += '<span class="hidable"> / </span>'.html_safe
end
- full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path')
+ full_title += group_title_link(group)
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
content_tag :span, class: 'group-title' do
@@ -56,4 +57,25 @@ module GroupsHelper
def group_issues(group)
IssuesFinder.new(current_user, group_id: group.id).execute
end
+
+ def remove_group_message(group)
+ _("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+ { group_name: group.name }
+ end
+
+ private
+
+ def group_title_link(group, hidable: false)
+ link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
+ output =
+ if show_new_nav?
+ image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
+ else
+ ""
+ end
+
+ output << simple_sanitize(group.name)
+ output.html_safe
+ end
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 3259a9c1933..d0c518f81f7 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -26,9 +26,9 @@ module IssuablesHelper
project = issuable.project
if issuable.is_a?(MergeRequest)
- namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+ project_merge_request_path(project, issuable.iid, :json)
else
- namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+ project_issue_path(project, issuable.iid, :json)
end
end
@@ -165,11 +165,7 @@ module IssuablesHelper
}
state_title = titles[state] || state.to_s.humanize
-
- count =
- Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
- issuables_count_for_state(issuable_type, state)
- end
+ count = issuables_count_for_state(issuable_type, state)
html = content_tag(:span, state_title)
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
@@ -201,7 +197,7 @@ module IssuablesHelper
def issuable_initial_data(issuable)
data = {
- endpoint: namespace_project_issue_path(@project.namespace, @project, issuable),
+ endpoint: project_issue_path(@project, issuable),
canUpdate: can?(current_user, :update_issue, issuable),
canDestroy: can?(current_user, :destroy_issue, issuable),
canMove: current_user ? issuable.can_move?(current_user) : false,
@@ -237,6 +233,65 @@ module IssuablesHelper
}
end
+ def issuables_count_for_state(issuable_type, state, finder: nil)
+ finder ||= public_send("#{issuable_type}_finder")
+ cache_key = finder.state_counter_cache_key(state)
+
+ @counts ||= {}
+ @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
+ finder.count_by_state
+ end
+
+ @counts[cache_key][state]
+ end
+
+ def close_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :close))
+ end
+
+ def reopen_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :reopen))
+ end
+
+ def close_reopen_issuable_url(issuable, should_inverse = false)
+ issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
+ end
+
+ def issuable_url(issuable, *options)
+ case issuable
+ when Issue
+ issue_url(issuable, *options)
+ when MergeRequest
+ merge_request_url(issuable, *options)
+ end
+ end
+
+ def issuable_button_visibility(issuable, closed)
+ case issuable
+ when Issue
+ issue_button_visibility(issuable, closed)
+ when MergeRequest
+ merge_request_button_visibility(issuable, closed)
+ end
+ end
+
+ def issuable_close_reopen_button_method(issuable)
+ case issuable
+ when Issue
+ ''
+ when MergeRequest
+ 'put'
+ end
+ end
+
+ def issuable_author_is_current_user(issuable)
+ issuable.author == current_user
+ end
+
+ def issuable_display_type(issuable)
+ issuable.model_name.human.downcase
+ end
+
private
def sidebar_gutter_collapsed?
@@ -255,24 +310,6 @@ module IssuablesHelper
end
end
- def issuables_count_for_state(issuable_type, state)
- @counts ||= {}
- @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
- @counts[issuable_type][state]
- end
-
- IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
- private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
-
- def issuables_state_counter_cache_key(issuable_type, state)
- opts = params.with_indifferent_access
- opts[:state] = state
- opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
- opts.delete_if { |_, value| value.blank? }
-
- hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
- end
-
def issuable_templates(issuable)
@issuable_templates ||=
case issuable
@@ -280,8 +317,6 @@ module IssuablesHelper
issue_template_names
when MergeRequest
merge_request_template_names
- else
- raise 'Unknown issuable type!'
end
end
@@ -305,10 +340,18 @@ module IssuablesHelper
mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
- url: namespace_project_todos_path(@project.namespace, @project),
+ url: project_todos_path(@project),
delete_path: (dashboard_todo_path(todo) if todo),
placement: (is_collapsed ? 'left' : nil),
container: (is_collapsed ? 'body' : nil)
}
end
+
+ def close_reopen_params(issuable, action)
+ {
+ issuable.model_name.to_s.underscore => { state_event: action }
+ }.tap do |params|
+ params[:format] = :json if issuable.is_a?(Issue)
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 82288f1da35..42b6cfdf02f 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -150,7 +150,7 @@ module IssuesHelper
Gitlab::UrlBuilder.build(single_discussion.first_note)
else
project = merge_request.project
- namespace_project_merge_request_path(project.namespace, project, merge_request)
+ project_merge_request_path(project, merge_request)
end
link_to link_text, path
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 6baf6f31d8f..4b99de1b6a5 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -57,14 +57,14 @@ module LabelsHelper
def edit_label_path(label)
case label
when GroupLabel then edit_group_label_path(label.group, label)
- when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then edit_project_label_path(label.project, label)
end
end
def destroy_label_path(label)
case label
when GroupLabel then group_label_path(label.group, label)
- when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then project_label_path(label.project, label)
end
end
@@ -127,7 +127,7 @@ module LabelsHelper
project = @target_project || @project
if project
- namespace_project_labels_path(project.namespace, project, :json)
+ project_labels_path(project, :json)
else
dashboard_labels_path(:json)
end
@@ -149,8 +149,8 @@ module LabelsHelper
case label_subscription_status(label, project)
when 'group-level' then toggle_subscription_group_label_path(label.group, label)
- when 'project-level' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
- when 'unsubscribed' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
+ when 'project-level' then toggle_subscription_project_label_path(project, label)
+ when 'unsubscribed' then toggle_subscription_project_label_path(project, label)
end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 39d30631646..78cf7b26a31 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,7 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
target_project = event.project.default_merge_request_target
- new_namespace_project_merge_request_path(
- event.project.namespace,
+ project_new_merge_request_path(
event.project,
new_mr_from_push_event(event, target_project)
)
@@ -48,8 +47,8 @@ module MergeRequestsHelper
end
def mr_change_branches_path(merge_request)
- new_namespace_project_merge_request_path(
- @project.namespace, @project,
+ project_new_merge_request_path(
+ @project,
merge_request: {
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
@@ -82,9 +81,7 @@ module MergeRequestsHelper
end
def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
- diffs_namespace_project_merge_request_path(
- project.namespace, project, merge_request,
- diff_id: merge_request_diff.id, start_sha: start_sha)
+ diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha)
end
def version_index(merge_request_diff)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index a230db22fa2..f8860bfee99 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,7 +1,7 @@
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
- namespace_project_milestones_path(@project.namespace, @project, opts)
+ project_milestones_path(@project, opts)
elsif @group
group_milestones_path(@group, opts)
else
@@ -11,7 +11,7 @@ module MilestonesHelper
def milestones_label_path(opts = {})
if @project
- namespace_project_issues_path(@project.namespace, @project, opts)
+ project_issues_path(@project, opts)
elsif @group
issues_group_path(@group, opts)
else
@@ -54,8 +54,10 @@ module MilestonesHelper
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
+ elsif param == check
+ 'active'
else
- 'active' if param == check
+ check
end
end
@@ -73,7 +75,9 @@ module MilestonesHelper
def milestones_filter_dropdown_path
project = @target_project || @project
if project
- namespace_project_milestones_path(project.namespace, project, :json)
+ project_milestones_path(project, :json)
+ elsif @group
+ group_milestones_path(@group, :json)
else
dashboard_milestones_path(:json)
end
@@ -118,7 +122,7 @@ module MilestonesHelper
def milestone_merge_request_tab_path(milestone)
if @project
- merge_requests_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ merge_requests_project_milestone_path(@project, milestone, format: :json)
elsif @group
merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
@@ -128,7 +132,7 @@ module MilestonesHelper
def milestone_participants_tab_path(milestone)
if @project
- participants_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ participants_project_milestone_path(@project, milestone, format: :json)
elsif @group
participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
@@ -138,11 +142,21 @@ module MilestonesHelper
def milestone_labels_tab_path(milestone)
if @project
- labels_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ labels_project_milestone_path(@project, milestone, format: :json)
elsif @group
labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
end
end
+
+ def group_milestone_route(milestone, params = {})
+ params = nil if params.empty?
+
+ if milestone.is_legacy_group_milestone?
+ group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: params)
+ else
+ group_milestone_path(@group, milestone.iid, milestone: params)
+ end
+ end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 833d3c36b28..b769462abc2 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,11 +1,7 @@
module NavHelper
def page_gutter_class
if current_path?('merge_requests#show') ||
- current_path?('merge_requests#diffs') ||
- current_path?('merge_requests#commits') ||
- current_path?('merge_requests#builds') ||
- current_path?('merge_requests#conflicts') ||
- current_path?('merge_requests#pipelines') ||
+ current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') ||
current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true'
@@ -27,7 +23,6 @@ module NavHelper
def nav_header_class
class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav
- class_name << " with-peek" if peek_enabled?
class_name
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 64ad7b280cb..0a0881d95cf 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -47,6 +47,18 @@ module NotesHelper
data
end
+ def add_diff_note_button(line_code, position, line_type)
+ return if @diff_notes_disabled
+
+ button_tag '',
+ class: 'add-diff-note js-add-diff-note-button',
+ type: 'submit', name: 'button',
+ data: diff_view_line_data(line_code, position, line_type),
+ title: 'Add a comment to this line' do
+ icon('comment-o')
+ end
+ end
+
def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user
@@ -69,11 +81,11 @@ module NotesHelper
path_params = version_params.merge(anchor: discussion.line_code)
- diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, path_params)
+ diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params)
elsif discussion.for_commit?
anchor = discussion.line_code if discussion.diff_discussion?
- namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor)
+ project_commit_path(discussion.project, discussion.noteable, anchor: anchor)
end
end
@@ -81,12 +93,7 @@ module NotesHelper
if @snippet.is_a?(PersonalSnippet)
snippet_notes_path(@snippet)
else
- namespace_project_noteable_notes_path(
- namespace_id: @project.namespace,
- project_id: @project,
- target_id: @noteable.id,
- target_type: @noteable.class.name.underscore
- )
+ project_noteable_notes_path(@project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)
end
end
@@ -94,7 +101,7 @@ module NotesHelper
if note.noteable.is_a?(PersonalSnippet)
snippet_note_path(note.noteable, note)
else
- namespace_project_note_path(project.namespace, project, note)
+ project_note_path(project, note)
end
end
diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb
new file mode 100644
index 00000000000..d24efe37f5f
--- /dev/null
+++ b/app/helpers/performance_bar_helper.rb
@@ -0,0 +1,7 @@
+module PerformanceBarHelper
+ # This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?`
+ # in WithPerformanceBar breaks tests (but works in the browser).
+ def performance_bar_enabled?
+ peek_enabled?
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c04b1419a19..25969adb649 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -58,7 +58,17 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
+ project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+ output =
+ if show_new_nav?
+ project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
+ else
+ ""
+ end
+
+ output << simple_sanitize(project.name)
+ output.html_safe
+ end
if current_user
project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
@@ -257,15 +267,15 @@ module ProjectsHelper
def tab_ability_map
{
- environments: :read_environment,
- milestones: :read_milestone,
- snippets: :read_project_snippet,
- settings: :admin_project,
- builds: :read_build,
- labels: :read_label,
- issues: :read_issue,
- team: :read_project_member,
- wiki: :read_wiki
+ environments: :read_environment,
+ milestones: :read_milestone,
+ snippets: :read_project_snippet,
+ settings: :admin_project,
+ builds: :read_build,
+ labels: :read_label,
+ issues: :read_issue,
+ project_members: :read_project_member,
+ wiki: :read_wiki
}
end
@@ -337,8 +347,7 @@ module ProjectsHelper
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase }
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: file_name,
@@ -349,8 +358,7 @@ module ProjectsHelper
end
def add_koding_stack_path(project)
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: '.koding.yml',
@@ -404,8 +412,7 @@ module ProjectsHelper
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
- namespace_project_blob_path(
- project.namespace,
+ project_blob_path(
project,
tree_join(project.default_branch,
contribution_guide.name)
@@ -435,7 +442,7 @@ module ProjectsHelper
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
- namespace_project_wiki_path(proj.namespace, proj, page, url_params)
+ project_wiki_path(proj, page, url_params)
end
def project_status_css_class(status)
@@ -460,8 +467,7 @@ module ProjectsHelper
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
- namespace_project_blob_path(
- project.namespace,
+ project_blob_path(
project,
tree_join(project.default_branch, blob.name)
)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8f15904f068..fd7ab59ce64 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -67,16 +67,16 @@ module SearchHelper
ref = @ref || @project.repository.root_ref
[
- { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) },
- { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }
+ { category: "Current Project", label: "Files", url: project_tree_path(@project, ref) },
+ { category: "Current Project", label: "Commits", url: project_commits_path(@project, ref) },
+ { category: "Current Project", label: "Network", url: project_network_path(@project, ref) },
+ { category: "Current Project", label: "Graph", url: project_graph_path(@project, ref) },
+ { category: "Current Project", label: "Issues", url: project_issues_path(@project) },
+ { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) },
+ { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) },
+ { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) },
+ { category: "Current Project", label: "Members", url: project_project_members_path(@project) },
+ { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) }
]
else
[]
@@ -104,7 +104,7 @@ module SearchHelper
id: p.id,
value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.name_with_namespace)}",
- url: namespace_project_path(p.namespace, p)
+ url: project_path(p)
}
end
end
@@ -126,6 +126,18 @@ module SearchHelper
search_path(options)
end
+ def search_filter_input_options(type)
+ {
+ id: "filtered-search-#{type}",
+ placeholder: 'Search or filter results...',
+ data: {
+ 'project-id' => @project.id,
+ 'username-params' => @users.to_json(only: [:id, :username]),
+ 'base-endpoint' => project_path(@project)
+ }
+ }
+ end
+
# Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters.
def search_md_sanitize(object, field)
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 2fd64b3441e..b447d4952e7 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,8 +1,7 @@
module SnippetsHelper
def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id?
- namespace_project_snippet_path(snippet.project.namespace,
- snippet.project, snippet, opts)
+ project_snippet_path(snippet.project, snippet, opts)
else
snippet_path(snippet, opts)
end
@@ -10,7 +9,7 @@ module SnippetsHelper
def download_snippet_path(snippet)
if snippet.project_id
- raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
+ raw_project_snippet_path(@project, snippet, inline: false)
else
raw_snippet_path(snippet, inline: false)
end
@@ -21,7 +20,7 @@ module SnippetsHelper
# @returns String, path to snippet index
def subject_snippets_path(subject = nil, opts = nil)
if subject.is_a?(Project)
- namespace_project_snippets_path(subject.namespace, subject, opts)
+ project_snippets_path(subject, opts)
else # assume subject === User
dashboard_snippets_path(opts)
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 8e0a1e2ecdf..b24039fb349 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -73,6 +73,7 @@ module SubmoduleHelper
end
def relative_self_links(url, commit)
+ url.rstrip!
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 1a55ee05996..ee701076a14 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -107,8 +107,7 @@ module TabHelper
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
- current_page?(namespace_project_repository_path(@project.namespace,
- @project))
+ current_page?(project_repository_path(@project))
'active'
end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index 31aaf9e5607..d000d6b1c0a 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -10,7 +10,7 @@ module TagsHelper
}
options = exist_opts.merge(options)
- namespace_project_tags_path(@project.namespace, @project, @id, options)
+ project_tags_path(@project, @id, options)
end
def tag_list(project)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 3d1b3a4711a..2a7aa299e83 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -39,7 +39,7 @@ module TodosHelper
anchor = dom_id(todo.note) if todo.note.present?
if todo.for_commit?
- namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+ project_commit_path(todo.project,
todo.target, anchor: anchor)
else
path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 6bacda9fe75..0386df22374 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -11,20 +11,29 @@ module WebpackHelper
paths = Webpack::Rails::Manifest.asset_paths(source)
if extension
- paths = paths.select { |p| p.ends_with? ".#{extension}" }
+ paths.select! { |p| p.ends_with? ".#{extension}" }
end
- # include full webpack-dev-server url for rspec tests running locally
+ force_host = webpack_public_host
+ if force_host
+ paths.map! { |p| "#{force_host}#{p}" }
+ end
+
+ paths
+ end
+
+ def webpack_public_host
if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
host = Rails.configuration.webpack.dev_server.host
port = Rails.configuration.webpack.dev_server.port
protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
-
- paths.map! do |p|
- "#{protocol}://#{host}:#{port}#{p}"
- end
+ "#{protocol}://#{host}:#{port}"
+ else
+ ActionController::Base.asset_host.try(:chomp, '/')
end
+ end
- paths
+ def webpack_public_path
+ "#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/"
end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 0f847841295..64ca2d2eacf 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -31,7 +31,7 @@ module Emails
setup_issue_mail(issue_id, recipient_id)
@label_names = label_names
- @labels_url = namespace_project_labels_url(@project.namespace, @project)
+ @labels_url = project_labels_url(@project)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
end
@@ -56,7 +56,7 @@ module Emails
def setup_issue_mail(issue_id, recipient_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
+ @target_url = project_issue_url(@project, @issue)
@sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index ec27ac517db..3626f8ce416 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -22,7 +22,7 @@ module Emails
setup_merge_request_mail(merge_request_id, recipient_id)
@label_names = label_names
- @labels_url = namespace_project_labels_url(@project.namespace, @project)
+ @labels_url = project_labels_url(@project)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
end
@@ -59,7 +59,7 @@ module Emails
def setup_merge_request_mail(merge_request_id, recipient_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request)
+ @target_url = project_merge_request_url(@project, @merge_request)
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 00707a0023e..77a82b895ce 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,7 +4,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@commit = @note.noteable
- @target_url = namespace_project_commit_url(*note_target_url_options)
+ @target_url = project_commit_url(*note_target_url_options)
mail_answer_thread(@commit, note_thread_options(recipient_id))
end
@@ -12,7 +12,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@issue = @note.noteable
- @target_url = namespace_project_issue_url(*note_target_url_options)
+ @target_url = project_issue_url(*note_target_url_options)
mail_answer_thread(@issue, note_thread_options(recipient_id))
end
@@ -20,7 +20,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@merge_request = @note.noteable
- @target_url = namespace_project_merge_request_url(*note_target_url_options)
+ @target_url = project_merge_request_url(*note_target_url_options)
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
@@ -28,7 +28,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
- @target_url = namespace_project_snippet_url(*note_target_url_options)
+ @target_url = project_snippet_url(*note_target_url_options)
mail_answer_thread(@snippet, note_thread_options(recipient_id))
end
@@ -43,7 +43,7 @@ module Emails
private
def note_target_url_options
- [@project.namespace, @project, @note.noteable, anchor: "note_#{@note.id}"]
+ [@project, @note.noteable, anchor: "note_#{@note.id}"]
end
def note_thread_options(recipient_id)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index e0af7081411..761d873c01c 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,7 +3,7 @@ module Emails
def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id
@project = Project.find project_id
- @target_url = namespace_project_url(@project.namespace, @project)
+ @target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email,
subject: subject("Project was moved"))
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index f315e38bcaa..eaac6fcb548 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,5 +1,6 @@
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
+ include GitlabRoutingHelper
include Emails::Issues
include Emails::MergeRequests
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f3692a5a067..0b6bcbde5d9 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,35 +1,20 @@
+require_dependency 'declarative_policy'
+
class Ability
class << self
# Given a list of users and a project this method returns the users that can
# read the given project.
def users_that_can_read_project(users, project)
- if project.public?
- users
- else
- users.select do |user|
- if user.admin?
- true
- elsif project.internal? && !user.external?
- true
- elsif project.owner == user
- true
- elsif project.team.members.include?(user)
- true
- else
- false
- end
- end
+ DeclarativePolicy.subject_scope do
+ users.select { |u| allowed?(u, :read_project, project) }
end
end
# Given a list of users and a snippet this method returns the users that can
# read the given snippet.
def users_that_can_read_personal_snippet(users, snippet)
- case snippet.visibility_level
- when Snippet::INTERNAL, Snippet::PUBLIC
- users
- when Snippet::PRIVATE
- users.include?(snippet.author) ? [snippet.author] : []
+ DeclarativePolicy.subject_scope do
+ users.select { |u| allowed?(u, :read_personal_snippet, snippet) }
end
end
@@ -38,42 +23,35 @@ class Ability
# issues - The issues to reduce down to those readable by the user.
# user - The User for which to check the issues
def issues_readable_by_user(issues, user = nil)
- return issues if user && user.admin?
-
- issues.select { |issue| issue.visible_to_user?(user) }
+ DeclarativePolicy.user_scope do
+ issues.select { |issue| issue.visible_to_user?(user) }
+ end
end
- # TODO: make this private and use the actual abilities stuff for this
def can_edit_note?(user, note)
- return false if !note.editable? || !user.present?
- return true if note.author == user || user.admin?
-
- if note.project
- max_access_level = note.project.team.max_member_access(user.id)
- max_access_level >= Gitlab::Access::MASTER
- else
- false
- end
+ allowed?(user, :edit_note, note)
end
- def allowed?(user, action, subject = :global)
- allowed(user, subject).include?(action)
- end
+ def allowed?(user, action, subject = :global, opts = {})
+ if subject.is_a?(Hash)
+ opts, subject = subject, :global
+ end
- def allowed(user, subject = :global)
- return BasePolicy::RuleSet.none if subject.nil?
- return uncached_allowed(user, subject) unless RequestStore.active?
+ policy = policy_for(user, subject)
- user_key = user ? user.id : 'anonymous'
- subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}"
- key = "/ability/#{user_key}/#{subject_key}"
- RequestStore[key] ||= uncached_allowed(user, subject).freeze
+ case opts[:scope]
+ when :user
+ DeclarativePolicy.user_scope { policy.can?(action) }
+ when :subject
+ DeclarativePolicy.subject_scope { policy.can?(action) }
+ else
+ policy.can?(action)
+ end
end
- private
-
- def uncached_allowed(user, subject)
- BasePolicy.class_for(subject).abilities(user, subject)
+ def policy_for(user, subject = :global)
+ cache = RequestStore.active? ? RequestStore : {}
+ DeclarativePolicy.policy_for(user, subject, cache: cache)
end
end
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index c79326e8427..f9c48482be7 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -10,5 +10,5 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 668caef0d2c..98e3906a932 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -13,13 +13,13 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters
}x
- serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize
- serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize
- serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize
- serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiveRecordSerialize
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
@@ -184,6 +184,9 @@ class ApplicationSetting < ActiveRecord::Base
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
+ rescue
+ # Fall back to an uncached value if there are any problems (e.g. redis down)
+ ApplicationSetting.last
end
def self.expire
@@ -234,6 +237,7 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
+ performance_bar_allowed_group_id: nil,
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
@@ -336,6 +340,48 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
+ def performance_bar_allowed_group_id=(group_full_path)
+ group_full_path = nil if group_full_path.blank?
+
+ if group_full_path.nil?
+ if group_full_path != performance_bar_allowed_group_id
+ super(group_full_path)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ return
+ end
+
+ group = Group.find_by_full_path(group_full_path)
+
+ if group
+ if group.id != performance_bar_allowed_group_id
+ super(group.id)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ else
+ super(nil)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ end
+
+ def performance_bar_allowed_group
+ Group.find_by_id(performance_bar_allowed_group_id)
+ end
+
+ # Return true if the Performance Bar is enabled for a given group
+ def performance_bar_enabled
+ performance_bar_allowed_group_id.present?
+ end
+
+ # - If `enable` is true, we early return since the actual attribute that holds
+ # the enabling/disabling is `performance_bar_allowed_group_id`
+ # - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil`
+ def performance_bar_enabled=(enable)
+ return if enable
+
+ self.performance_bar_allowed_group_id = nil
+ end
+
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 46d412fbd72..112a8778b4e 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,5 +1,5 @@
class AuditEvent < ActiveRecord::Base
- serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb
index 75c373a03bb..4604a9934a0 100644
--- a/app/models/blob_viewer/readme.rb
+++ b/app/models/blob_viewer/readme.rb
@@ -10,5 +10,11 @@ module BlobViewer
def visible_to?(current_user)
can?(current_user, :read_wiki, project)
end
+
+ def render_error
+ return if project.has_external_wiki? || (project.wiki_enabled? && project.wiki.has_home_page?)
+
+ :no_wiki
+ end
end
end
diff --git a/app/models/board.rb b/app/models/board.rb
index 18081a32157..97d0f550925 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -1,7 +1,7 @@
class Board < ActiveRecord::Base
belongs_to :project
- has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
+ has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
validates :project, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a300536532b..432f3f242eb 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -19,8 +19,8 @@ module Ci
)
end
- serialize :options # rubocop:disable Cop/ActiverecordSerialize
- serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize
+ serialize :options # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize
delegate :name, to: :project, prefix: true
@@ -176,13 +176,22 @@ module Ci
# * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes
+ # * First/Last Character is not a hyphen
def ref_slug
- slugified = ref.to_s.downcase
- slugified.gsub(/[^a-z0-9]/, '-')[0..62]
+ ref.to_s
+ .downcase
+ .gsub(/[^a-z0-9]/, '-')[0..62]
+ .gsub(/(\A-+|-+\z)/, '')
end
# Variables whose value does not depend on environment
def simple_variables
+ variables(environment: nil)
+ end
+
+ # All variables, including those dependent on environment, which could
+ # contain unexpanded variables.
+ def variables(environment: persisted_environment)
variables = predefined_variables
variables += project.predefined_variables
variables += pipeline.predefined_variables
@@ -191,15 +200,13 @@ module Ci
variables += project.deployment_variables if has_environment?
variables += yaml_variables
variables += user_variables
- variables += project.secret_variables_for(ref).map(&:to_runner_variable)
+ variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
+ variables += secret_variables(environment: environment)
variables += trigger_request.user_variables if trigger_request
- variables
- end
+ variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule
+ variables += persisted_environment_variables if environment
- # All variables, including those dependent on environment, which could
- # contain unexpanded variables.
- def variables
- simple_variables.concat(persisted_environment_variables)
+ variables
end
def merge_request
@@ -213,7 +220,7 @@ module Ci
.reorder(iid: :desc)
merge_requests.find do |merge_request|
- merge_request.commits_sha.include?(pipeline.sha)
+ merge_request.commit_shas.include?(pipeline.sha)
end
end
end
@@ -367,6 +374,11 @@ module Ci
]
end
+ def secret_variables(environment: persisted_environment)
+ project.secret_variables_for(ref: ref, environment: environment)
+ .map(&:to_runner_variable)
+ end
+
def steps
[Gitlab::Ci::Build::Step.from_commands(self),
Gitlab::Ci::Build::Step.from_after_script(self)].compact
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
new file mode 100644
index 00000000000..f64bc245a67
--- /dev/null
+++ b/app/models/ci/group_variable.rb
@@ -0,0 +1,13 @@
+module Ci
+ class GroupVariable < ActiveRecord::Base
+ extend Ci::Model
+ include HasVariable
+ include Presentable
+
+ belongs_to :group
+
+ validates :key, uniqueness: { scope: :group_id }
+
+ scope :unprotected, -> { where(protected: false) }
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 1b3e5a25ac2..b646b32fc64 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -14,7 +14,7 @@ module Ci
has_many :stages
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, foreign_key: :commit_id
- has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
+ has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
@@ -140,6 +140,7 @@ module Ci
where(id: max_id)
end
end
+ scope :internal, -> { where(source: internal_sources) }
def self.latest_status(ref = nil)
latest(ref).status
@@ -163,6 +164,10 @@ module Ci
where.not(duration: nil).sum(:duration)
end
+ def self.internal_sources
+ sources.reject { |source| source == "external" }.values
+ end
+
def stages_count
statuses.select(:stage).distinct.count
end
@@ -321,10 +326,24 @@ module Ci
end
end
+ def ci_yaml_file_path
+ if project.ci_config_path.blank?
+ '.gitlab-ci.yml'
+ else
+ project.ci_config_path
+ end
+ end
+
def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
- @ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil
+ @ci_yaml_file = begin
+ project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
+ rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal
+ self.yaml_errors =
+ "Failed to load CI/CD config file at #{ci_yaml_file_path}"
+ nil
+ end
end
def has_yaml_errors?
@@ -372,7 +391,8 @@ module Ci
def predefined_variables
[
- { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
+ { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
+ { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }
]
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 45d8cd34359..e4ae1b35f66 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -9,17 +9,21 @@ module Ci
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
has_many :pipelines
+ has_many :variables, class_name: 'Ci::PipelineScheduleVariable'
validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :description, presence: true
+ validates :variables, variable_duplicates: true
before_save :set_next_run_at
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
+ accepts_nested_attributes_for :variables, allow_destroy: true
+
def owned_by?(current_user)
owner == current_user
end
@@ -56,5 +60,9 @@ module Ci
Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone)
.next_time_from(next_run_at)
end
+
+ def job_variables
+ variables&.map(&:to_runner_variable) || []
+ end
end
end
diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb
new file mode 100644
index 00000000000..1ff177616e8
--- /dev/null
+++ b/app/models/ci/pipeline_schedule_variable.rb
@@ -0,0 +1,8 @@
+module Ci
+ class PipelineScheduleVariable < ActiveRecord::Base
+ extend Ci::Model
+ include HasVariable
+
+ belongs_to :pipeline_schedule
+ end
+end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index d12f96f3d0b..fb0fbb43fb1 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -3,12 +3,12 @@ module Ci
extend Ci::Model
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
- LAST_CONTACT_TIME = 1.hour.ago
+ ONLINE_CONTACT_TIMEOUT = 1.hour
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds
- has_many :runner_projects, dependent: :destroy
+ has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
@@ -19,7 +19,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
- scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
+ scope :online, ->() { where('contacted_at > ?', contact_time_deadline) }
scope :ordered, ->() { order(id: :desc) }
scope :owned_or_shared, ->(project_id) do
@@ -59,6 +59,10 @@ module Ci
where(t[:token].matches(pattern).or(t[:description].matches(pattern)))
end
+ def self.contact_time_deadline
+ ONLINE_CONTACT_TIMEOUT.ago
+ end
+
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
@@ -80,7 +84,7 @@ module Ci
end
def online?
- contacted_at && contacted_at > LAST_CONTACT_TIME
+ contacted_at && contacted_at > self.class.contact_time_deadline
end
def status
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 564334ad1ad..c58ce5c3717 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -6,7 +6,7 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id
has_many :builds
- serialize :variables # rubocop:disable Cop/ActiverecordSerialize
+ serialize :variables # rubocop:disable Cop/ActiveRecordSerialize
def user_variables
return [] unless variables
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index f235260208f..cf0fe04ddaf 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,27 +1,13 @@
module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
+ include HasVariable
+ include Presentable
belongs_to :project
- validates :key,
- presence: true,
- uniqueness: { scope: :project_id },
- length: { maximum: 255 },
- format: { with: /\A[a-zA-Z0-9_]+\z/,
- message: "can contain only letters, digits and '_'." }
+ validates :key, uniqueness: { scope: [:project_id, :environment_scope] }
- scope :order_key_asc, -> { reorder(key: :asc) }
scope :unprotected, -> { where(protected: false) }
-
- attr_encrypted :value,
- mode: :per_attribute_iv_and_salt,
- insecure_mode: true,
- key: Gitlab::Application.secrets.db_key_base,
- algorithm: 'aes-256-cbc'
-
- def to_runner_variable
- { key: key, value: value, public: false }
- end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 20206d57c4c..c7f62617c4c 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -138,7 +138,7 @@ class Commit
safe_message.split("\n", 2)[1].try(:chomp)
end
-
+
def description?
description.present?
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index a7fd0a15f0f..f4f9b037957 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,7 +2,7 @@ module Awardable
extend ActiveSupport::Concern
included do
- has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy
+ has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
if self < Participable
# By default we always load award_emoji user association
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index eb32bf3d32a..95152dcd68c 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
- cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil?
+ cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present?
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false
diff --git a/app/models/concerns/created_at_filterable.rb b/app/models/concerns/created_at_filterable.rb
new file mode 100644
index 00000000000..e8a3e41203d
--- /dev/null
+++ b/app/models/concerns/created_at_filterable.rb
@@ -0,0 +1,12 @@
+module CreatedAtFilterable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :created_before, ->(date) { where(scoped_table[:created_at].lteq(date)) }
+ scope :created_after, ->(date) { where(scoped_table[:created_at].gteq(date)) }
+
+ def self.scoped_table
+ arel_table.alias(table_name)
+ end
+ end
+end
diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb
new file mode 100644
index 00000000000..6ddbb8da1a9
--- /dev/null
+++ b/app/models/concerns/each_batch.rb
@@ -0,0 +1,81 @@
+module EachBatch
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Iterates over the rows in a relation in batches, similar to Rails'
+ # `in_batches` but in a more efficient way.
+ #
+ # Unlike `in_batches` provided by Rails this method does not support a
+ # custom start/end range, nor does it provide support for the `load:`
+ # keyword argument.
+ #
+ # This method will yield an ActiveRecord::Relation to the supplied block, or
+ # return an Enumerator if no block is given.
+ #
+ # Example:
+ #
+ # User.each_batch do |relation|
+ # relation.update_all(updated_at: Time.now)
+ # end
+ #
+ # The supplied block is also passed an optional batch index:
+ #
+ # User.each_batch do |relation, index|
+ # puts index # => 1, 2, 3, ...
+ # end
+ #
+ # You can also specify an alternative column to use for ordering the rows:
+ #
+ # User.each_batch(column: :created_at) do |relation|
+ # ...
+ # end
+ #
+ # This will produce SQL queries along the lines of:
+ #
+ # User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
+ # (0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
+ #
+ # of - The number of rows to retrieve per batch.
+ # column - The column to use for ordering the batches.
+ def each_batch(of: 1000, column: primary_key)
+ unless column
+ raise ArgumentError,
+ 'the column: argument must be set to a column name to use for ordering rows'
+ end
+
+ start = except(:select)
+ .select(column)
+ .reorder(column => :asc)
+ .take
+
+ return unless start
+
+ start_id = start[column]
+ arel_table = self.arel_table
+
+ 1.step do |index|
+ stop = except(:select)
+ .select(column)
+ .where(arel_table[column].gteq(start_id))
+ .reorder(column => :asc)
+ .offset(of)
+ .limit(1)
+ .take
+
+ relation = where(arel_table[column].gteq(start_id))
+
+ if stop
+ stop_id = stop[column]
+ start_id = stop_id
+ relation = relation.where(arel_table[column].lt(stop_id))
+ end
+
+ # Any ORDER BYs are useless for this relation and can lead to less
+ # efficient UPDATE queries, hence we get rid of it.
+ yield relation.except(:order), index
+
+ break unless stop
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb
new file mode 100644
index 00000000000..5db64fe82c4
--- /dev/null
+++ b/app/models/concerns/feature_gate.rb
@@ -0,0 +1,7 @@
+module FeatureGate
+ def flipper_id
+ return nil if new_record?
+
+ "#{self.class.name}:#{id}"
+ end
+end
diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb
new file mode 100644
index 00000000000..9585b5583dc
--- /dev/null
+++ b/app/models/concerns/has_variable.rb
@@ -0,0 +1,23 @@
+module HasVariable
+ extend ActiveSupport::Concern
+
+ included do
+ validates :key,
+ presence: true,
+ length: { maximum: 255 },
+ format: { with: /\A[a-zA-Z0-9_]+\z/,
+ message: "can contain only letters, digits and '_'." }
+
+ scope :order_key_asc, -> { reorder(key: :asc) }
+
+ attr_encrypted :value,
+ mode: :per_attribute_iv_and_salt,
+ insecure_mode: true,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
+
+ def to_runner_variable
+ { key: key, value: value, public: false }
+ end
+ end
+end
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 5382dde6765..67a0adfcd56 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -8,7 +8,8 @@ module InternalId
def set_iid
if iid.blank?
- records = project.send(self.class.name.tableize)
+ parent = project || group
+ records = parent.send(self.class.name.tableize)
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d178ee4422b..13fe9d09c69 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -30,7 +30,8 @@ module Issuable
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
belongs_to :milestone
- has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
+
+ has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do # rubocop:disable Cop/ActiveRecordDependent
def authors_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
@@ -42,9 +43,9 @@ module Issuable
end
end
- has_many :label_links, as: :target, dependent: :destroy
+ has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, through: :label_links
- has_many :todos, as: :target, dependent: :destroy
+ has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :metrics
@@ -102,6 +103,14 @@ module Issuable
def locking_enabled?
title_changed? || description_changed?
end
+
+ def allows_multiple_assignees?
+ false
+ end
+
+ def has_multiple_assignees?
+ assignees.count > 1
+ end
end
module ClassMethods
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index 1848230ec7e..2d86a70c395 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -14,7 +14,7 @@ module Mentionable
end
EXTERNAL_PATTERN = begin
- issue_pattern = ExternalIssue.reference_pattern
+ issue_pattern = IssueTrackerService.reference_pattern
link_patterns = URI.regexp(%w(http https))
reference_pattern(link_patterns, issue_pattern)
end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 01599ce49c6..f0998465822 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -70,6 +70,22 @@ module Milestoneish
due_date && due_date.past?
end
+ def is_group_milestone?
+ false
+ end
+
+ def is_project_milestone?
+ false
+ end
+
+ def is_legacy_group_milestone?
+ false
+ end
+
+ def is_dashboard_milestone?
+ false
+ end
+
private
def count_issues_by_state(user)
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index 47e71c58557..fc6b840f7a8 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -17,7 +17,7 @@ module ProtectedRef
class_methods do
def protected_ref_access_levels(*types)
types.each do |type|
- has_many :"#{type}_access_levels", dependent: :destroy
+ has_many :"#{type}_access_levels", dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." }
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index ec7796a9dbb..f5048d17d80 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -4,8 +4,8 @@ module Routable
extend ActiveSupport::Concern
included do
- has_one :route, as: :source, autosave: true, dependent: :destroy
- has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy
+ has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates_associated :route
validates :route, presence: true
@@ -103,8 +103,12 @@ module Routable
def full_path
return uncached_full_path unless RequestStore.active?
- key = "routable/full_path/#{self.class.name}/#{self.id}"
- RequestStore[key] ||= uncached_full_path
+ RequestStore[full_path_key] ||= uncached_full_path
+ end
+
+ def expires_full_path_cache
+ RequestStore.delete(full_path_key) if RequestStore.active?
+ @full_path = nil
end
def build_full_path
@@ -135,6 +139,10 @@ module Routable
path_changed? || parent_changed?
end
+ def full_path_key
+ @full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}"
+ end
+
def build_full_name
if parent && name
parent.human_name + ' / ' + name
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
new file mode 100644
index 00000000000..67ecf470f7e
--- /dev/null
+++ b/app/models/concerns/sha_attribute.rb
@@ -0,0 +1,20 @@
+module ShaAttribute
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def sha_attribute(name)
+ return unless table_exists?
+
+ column = columns.find { |c| c.name == name.to_s }
+
+ # In case the table doesn't exist we won't be able to find the column,
+ # thus we will only check the type if the column is present.
+ if column && column.type != :binary
+ raise ArgumentError,
+ "sha_attribute #{name.inspect} is invalid since the column type is not :binary"
+ end
+
+ attribute(name, Gitlab::Database::ShaAttribute.new)
+ end
+ end
+end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index a155a064032..fdacfa5a194 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -5,6 +5,25 @@
module Sortable
extend ActiveSupport::Concern
+ module DropDefaultScopeOnFinders
+ # Override these methods to drop the `ORDER BY id DESC` default scope.
+ # See http://dba.stackexchange.com/a/110919 for why we do this.
+ %i[find find_by find_by!].each do |meth|
+ define_method meth do |*args, &block|
+ return super(*args, &block) if block
+
+ unordered_relation = unscope(:order)
+
+ # We cannot simply call `meth` on `unscope(:order)`, since that is also
+ # an instance of the same relation class this module is included into,
+ # which means we'd get infinite recursion.
+ # We explicitly use the original implementation to prevent this.
+ original_impl = method(__method__).super_method.unbind
+ original_impl.bind(unordered_relation).call(*args)
+ end
+ end
+ end
+
included do
# By default all models should be ordered
# by created_at field starting from newest
@@ -18,6 +37,10 @@ module Sortable
scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) }
+
+ # All queries (relations) on this model are instances of this `relation_klass`.
+ relation_klass = relation_delegate_class(ActiveRecord::Relation)
+ relation_klass.prepend DropDefaultScopeOnFinders
end
module ClassMethods
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 647a6cad3d7..bd75f25a210 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -8,7 +8,7 @@ module Spammable
end
included do
- has_one :user_agent_detail, as: :subject, dependent: :destroy
+ has_one :user_agent_detail, as: :subject, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
attr_accessor :spam
attr_accessor :spam_log
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index f60a0f8f438..274b38a7708 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -9,7 +9,7 @@ module Subscribable
extend ActiveSupport::Concern
included do
- has_many :subscriptions, dependent: :destroy, as: :subscribable
+ has_many :subscriptions, dependent: :destroy, as: :subscribable # rubocop:disable Cop/ActiveRecordDependent
end
def subscribed?(user, project = nil)
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
index 9cf83440784..b517ddaebd7 100644
--- a/app/models/concerns/time_trackable.rb
+++ b/app/models/concerns/time_trackable.rb
@@ -18,7 +18,7 @@ module TimeTrackable
validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false
validate :check_negative_time_spent
- has_many :timelogs, dependent: :destroy
+ has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end
def spend_time(options)
diff --git a/app/models/dashboard_milestone.rb b/app/models/dashboard_milestone.rb
index 646c1e5ce1a..fac7c5e5c85 100644
--- a/app/models/dashboard_milestone.rb
+++ b/app/models/dashboard_milestone.rb
@@ -2,4 +2,8 @@ class DashboardMilestone < GlobalMilestone
def issues_finder_params
{ authorized_only: true }
end
+
+ def is_dashboard_milestone?
+ true
+ end
end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 053f2a11aa0..51768dd96bc 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,5 +1,5 @@
class DeployKey < Key
- has_many :deploy_keys_projects, dependent: :destroy
+ has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 20ef1378500..e9a60e6ce09 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -6,9 +6,9 @@ class DiffNote < Note
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
- serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
- serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
- serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
validates :original_position, presence: true
validates :position, presence: true
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 66c96d0f586..e9ebf0637f3 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -6,7 +6,7 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true
- has_many :deployments, dependent: :destroy
+ has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
before_validation :nullify_external_url
@@ -218,8 +218,7 @@ class Environment < ActiveRecord::Base
end
def etag_cache_key
- Gitlab::Routing.url_helpers.namespace_project_environments_path(
- project.namespace,
+ Gitlab::Routing.url_helpers.project_environments_path(
project,
format: :json)
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 29bc141c5cd..8d93a228494 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -50,7 +50,7 @@ class Event < ActiveRecord::Base
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
# For Hash only
- serialize :data # rubocop:disable Cop/ActiverecordSerialize
+ serialize :data # rubocop:disable Cop/ActiveRecordSerialize
# Callbacks
after_create :reset_project_activity
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index e63f89a9f85..0bf18e529f0 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -38,11 +38,6 @@ class ExternalIssue
@project.id
end
- # Pattern used to extract `JIRA-123` issue references from text
- def self.reference_pattern
- @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
- end
-
def to_reference(_from_project = nil, full: nil)
id
end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index 36cf7ad6a28..8d35864eff6 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -1,4 +1,4 @@
class ForkedProjectLink < ActiveRecord::Base
- belongs_to :forked_to_project, class_name: 'Project'
- belongs_to :forked_from_project, class_name: 'Project'
+ belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
+ belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 698a7bbd327..2a1b7564962 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,7 +2,7 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
- delegate :color, :description, to: :@first_label
+ delegate :color, :text_color, :description, to: :@first_label
def for_display
@first_label
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 538615130a7..c0864769314 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -2,6 +2,7 @@ class GlobalMilestone
include Milestoneish
EPOCH = DateTime.parse('1970-01-01')
+ STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -11,7 +12,10 @@ class GlobalMilestone
end
def self.build_collection(projects, params)
- child_milestones = MilestonesFinder.new.execute(projects, params)
+ params =
+ { project_ids: projects.map(&:id), state: params[:state] }
+
+ child_milestones = MilestonesFinder.new(params).execute
milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped|
milestones_relation = Milestone.where(id: grouped.map(&:id))
@@ -28,13 +32,42 @@ class GlobalMilestone
new(title, child_milestones)
end
- def self.states_count(projects)
- relation = MilestonesFinder.new.execute(projects, state: 'all')
- milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
+ def self.states_count(projects, group = nil)
+ legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
+ group_milestones_count = group_milestones_states_count(group)
+
+ legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count|
+ legacy_group_milestones_count + group_milestones_count
+ end
+ end
+
+ def self.group_milestones_states_count(group)
+ return STATE_COUNT_HASH unless group
+
+ params = { group_ids: [group.id], state: 'all', order: nil }
+
+ relation = MilestonesFinder.new(params).execute
+ grouped_by_state = relation.group(:state).count
+
+ {
+ opened: grouped_by_state['active'] || 0,
+ closed: grouped_by_state['closed'] || 0,
+ all: grouped_by_state.values.sum
+ }
+ end
+
+ # Counts the legacy group milestones which must be grouped by title
+ def self.legacy_group_milestone_states_count(projects)
+ return STATE_COUNT_HASH unless projects
+
+ params = { project_ids: projects.map(&:id), state: 'all', order: nil }
+
+ relation = MilestonesFinder.new(params).execute
+ project_milestones_by_state_and_title = relation.group(:state, :title).count
- opened = count_by_state(milestones_by_state_and_title, 'active')
- closed = count_by_state(milestones_by_state_and_title, 'closed')
- all = milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
+ opened = count_by_state(project_milestones_by_state_and_title, 'active')
+ closed = count_by_state(project_milestones_by_state_and_title, 'closed')
+ all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
{
opened: opened,
diff --git a/app/models/group.rb b/app/models/group.rb
index 0b93460d473..70a4ceeffd8 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -8,7 +8,7 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
- has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
+ has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
has_many :users, through: :group_members
has_many :owners,
@@ -16,12 +16,14 @@ class Group < Namespace
through: :group_members,
source: :user
- has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember'
+ has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
- has_many :project_group_links, dependent: :destroy
+ has_many :milestones
+ has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :shared_projects, through: :project_group_links, source: :project
- has_many :notification_settings, dependent: :destroy, as: :source
+ has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, class_name: 'GroupLabel'
+ has_many :variables, class_name: 'Ci::GroupVariable'
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
@@ -31,7 +33,7 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -222,6 +224,12 @@ class Group < Namespace
User.where(id: members_with_parents.select(:user_id))
end
+ def users_with_descendants
+ members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id))
+
+ User.where(id: members_with_descendants.select(:user_id))
+ end
+
def max_member_access_for_user(user)
return GroupMember::OWNER if user.admin?
@@ -242,6 +250,14 @@ class Group < Namespace
}
end
+ def secret_variables_for(ref, project)
+ list_of_ids = [self] + ancestors
+ variables = Ci::GroupVariable.where(group: list_of_ids)
+ variables = variables.unprotected unless project.protected_for?(ref)
+ variables = variables.group_by(&:group_id)
+ list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
+ end
+
protected
def update_two_factor_requirement
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 86d38e5468b..65249bd7bfc 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -16,4 +16,8 @@ class GroupMilestone < GlobalMilestone
def issues_finder_params
{ group_id: group.id }
end
+
+ def is_legacy_group_milestone?
+ true
+ end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7503f3739c3..7a9f8997959 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -12,7 +12,7 @@ class WebHook < ActiveRecord::Base
default_value_for :repository_update_events, false
default_value_for :enable_ssl_verification, true
- has_many :web_hook_logs, dependent: :destroy
+ has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index d73cfcf630d..e72c125fb69 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,9 +1,9 @@
class WebHookLog < ActiveRecord::Base
belongs_to :web_hook
- serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
- serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize
- serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :request_data, Hash # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :response_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
validates :web_hook, presence: true
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 3a9a6dba601..400bb55d2f0 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys
include RelativePositioning
include IgnorableColumn
+ include CreatedAtFilterable
ignore_column :position
@@ -23,9 +24,14 @@ class Issue < ActiveRecord::Base
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ has_many :merge_requests_closing_issues,
+ class_name: 'MergeRequestsClosingIssues',
+ dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
+ has_many :issue_assignees
+ has_many :assignees, class_name: "User", through: :issue_assignees
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
@@ -45,8 +51,6 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
- scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
-
scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache
@@ -295,11 +299,7 @@ class Issue < ActiveRecord::Base
end
def expire_etag_cache
- key = Gitlab::Routing.url_helpers.realtime_changes_namespace_project_issue_path(
- project.namespace,
- project,
- self
- )
+ key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
Gitlab::EtagCaching::Store.new.touch(key)
end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index ed6a8411da9..674bb3f2720 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -15,9 +15,9 @@ class Label < ActiveRecord::Base
default_value_for :color, DEFAULT_COLOR
- has_many :lists, dependent: :destroy
+ has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :priorities, class_name: 'LabelPriority'
- has_many :label_links, dependent: :destroy
+ has_many :label_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 2d5909ab25e..c36be956ff0 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -7,7 +7,7 @@
class LegacyDiffNote < Note
include NoteOnDiff
- serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize
+ serialize :st_diff # rubocop:disable Cop/ActiveRecordSerialize
validates :line_code, presence: true, line_code: true
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 7712d5783e0..b7cf96abe83 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,5 +1,5 @@
class LfsObject < ActiveRecord::Base
- has_many :lfs_objects_projects, dependent: :destroy
+ has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :lfs_objects_projects
validates :oid, presence: true, uniqueness: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c099d731082..815c5b43406 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -5,6 +5,7 @@ class MergeRequest < ActiveRecord::Base
include Referable
include Sortable
include IgnorableColumn
+ include CreatedAtFilterable
ignore_column :position
@@ -12,24 +13,26 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
- has_many :merge_request_diffs, dependent: :destroy
+ has_many :merge_request_diffs
has_one :merge_request_diff,
- -> { order('merge_request_diffs.id DESC') }
+ -> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ has_many :merge_requests_closing_issues,
+ class_name: 'MergeRequestsClosingIssues',
+ dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
belongs_to :assignee, class_name: "User"
- serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
- delegate :commits, :real_size, :commits_sha, :commits_count,
+ delegate :commits, :real_size, :commit_shas, :commits_count,
to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
@@ -197,11 +200,19 @@ class MergeRequest < ActiveRecord::Base
}
end
- # This method is needed for compatibility with issues to not mess view and other code
+ # These method are needed for compatibility with issues to not mess view and other code
def assignees
Array(assignee)
end
+ def assignee_ids
+ Array(assignee_id)
+ end
+
+ def assignee_ids=(ids)
+ write_attribute(:assignee_id, ids.last)
+ end
+
def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id
end
@@ -508,7 +519,7 @@ class MergeRequest < ActiveRecord::Base
def related_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
- commit_ids = commits.last(commits_for_notes_limit).map(&:id)
+ commit_ids = commit_shas.take(commits_for_notes_limit)
Note.where(
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
@@ -831,15 +842,18 @@ class MergeRequest < ActiveRecord::Base
return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines
- .where(sha: all_commits_sha, ref: source_branch)
+ .where(sha: all_commit_shas, ref: source_branch)
.order(id: :desc)
end
# Note that this could also return SHA from now dangling commits
#
- def all_commits_sha
+ def all_commit_shas
if persisted?
- merge_request_diffs.flat_map(&:commits_sha).uniq
+ column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)')
+ serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas)
+
+ (column_shas + serialised_shas).uniq
elsif compare_commits
compare_commits.to_a.reverse.map(&:id)
else
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index f1ee4d3f7a9..4b141945ab4 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -11,9 +11,10 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
+ has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
- serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
- serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
+ serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize
state_machine :state, initial: :empty do
state :collected
@@ -47,14 +48,13 @@ 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
- ensure_commits_sha
+ ensure_commit_shas
save_commits
- reload_commits
save_diffs
keep_around_commits
end
- def ensure_commits_sha
+ def ensure_commit_shas
merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
@@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base
# created before version 8.4 that does not store head_commit_sha in separate db field.
def head_commit_sha
if persisted? && super.nil?
- last_commit.try(:sha)
+ last_commit_sha
else
super
end
@@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits
- @commits ||= load_commits(st_commits)
+ @commits ||= load_commits
end
- def reload_commits
- @commits = nil
- commits
- end
-
- def last_commit
- commits.first
+ def last_commit_sha
+ commit_shas.first
end
def first_commit
@@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(head_commit_sha)
end
- def commits_sha
- st_commits.map { |commit| commit[:id] }
+ def commit_shas
+ if st_commits.present?
+ st_commits.map { |commit| commit[:id] }
+ else
+ merge_request_diff_commits.map(&:sha)
+ end
end
def diff_refs=(new_diff_refs)
@@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
- st_commits.count
+ if st_commits.present?
+ st_commits.size
+ else
+ merge_request_diff_commits.size
+ end
end
def utf8_st_diffs
@@ -231,29 +234,6 @@ class MergeRequestDiff < ActiveRecord::Base
raw.any? { |element| VALID_CLASSES.include?(element.class) }
end
- def dump_commits(commits)
- commits.map(&:to_hash)
- end
-
- def load_commits(array)
- array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
- end
-
- # Load all commits related to current merge request diff from repo
- # and save it as array of hashes in st_commits db field
- def save_commits
- new_attributes = {}
-
- commits = compare.commits
-
- if commits.present?
- commits = Commit.decorate(commits, merge_request.source_project).reverse
- new_attributes[:st_commits] = dump_commits(commits)
- end
-
- update_columns_serialized(new_attributes)
- end
-
def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index|
diff.to_hash.merge(
@@ -294,12 +274,18 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
- # Load diffs between branches related to current merge request diff from repo
- # and save it as array of hashes in st_diffs db field
+ def load_commits
+ commits = st_commits.presence || merge_request_diff_commits
+
+ commits.map do |commit|
+ Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project)
+ end
+ end
+
def save_diffs
new_attributes = {}
- if commits.size.zero?
+ if compare.commits.size.zero?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
@@ -319,7 +305,13 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
- update_columns_serialized(new_attributes)
+ update(new_attributes)
+ end
+
+ def save_commits
+ MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
+
+ merge_request_diff_commits.reload
end
def repository
@@ -332,29 +324,6 @@ class MergeRequestDiff < ActiveRecord::Base
project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end
- #
- # #save or #update_attributes providing changes on serialized attributes do a lot of
- # serialization and deserialization calls resulting in bad performance.
- # Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide.
- # As a tradeoff we need to reload the current instance to properly manage time objects on those serialized
- # attributes. So to keep the same behaviour as the attribute assignment we reload the instance.
- # The difference is in the usage of
- # #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns)
- #
- # Ex:
- #
- # new_attributes[:st_commits].first.slice(:committed_date)
- # => {:committed_date=>2014-02-27 11:01:38 +0200}
- # YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date)))
- # => {:committed_date=>2014-02-27 10:01:38 +0100}
- #
- def update_columns_serialized(new_attributes)
- return unless new_attributes.any?
-
- update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
- reload
- end
-
def keep_around_commits
[repository, merge_request.source_project.repository].each do |repo|
repo.keep_around(start_commit_sha)
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
new file mode 100644
index 00000000000..cafdbe11849
--- /dev/null
+++ b/app/models/merge_request_diff_commit.rb
@@ -0,0 +1,38 @@
+class MergeRequestDiffCommit < ActiveRecord::Base
+ include ShaAttribute
+
+ belongs_to :merge_request_diff
+
+ sha_attribute :sha
+ alias_attribute :id, :sha
+
+ def self.create_bulk(merge_request_diff_id, commits)
+ sha_attribute = Gitlab::Database::ShaAttribute.new
+
+ rows = commits.map.with_index do |commit, index|
+ # See #parent_ids.
+ commit_hash = commit.to_hash.except(:parent_ids)
+ sha = commit_hash.delete(:id)
+
+ commit_hash.merge(
+ merge_request_diff_id: merge_request_diff_id,
+ relative_order: index,
+ sha: sha_attribute.type_cast_for_database(sha)
+ )
+ end
+
+ Gitlab::Database.bulk_insert(self.table_name, rows)
+ end
+
+ def to_hash
+ Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash|
+ hash[key] = public_send(key)
+ end
+ end
+
+ # We don't save these, because they would need a table or a serialised
+ # field. They aren't used anywhere, so just pretend the commit has no parents.
+ def parent_ids
+ []
+ end
+end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d2e2749f70d..48d00764965 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -18,17 +18,32 @@ class Milestone < ActiveRecord::Base
cache_markdown_field :description
belongs_to :project
+ belongs_to :group
+
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :of_groups, ->(ids) { where(group_id: ids) }
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
- scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :for_projects, -> { where(group: nil).includes(:project) }
+
+ scope :for_projects_and_groups, -> (project_ids, group_ids) do
+ conditions = []
+ conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any?
+ conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any?
+
+ where(conditions.reduce(:or))
+ end
+
+ validates :group, presence: true, unless: :project
+ validates :project, presence: true, unless: :group
- validates :title, presence: true, uniqueness: { scope: :project_id }
- validates :project, presence: true
+ validate :uniqueness_of_title, if: :title_changed?
+ validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
strip_attributes :title
@@ -63,6 +78,14 @@ class Milestone < ActiveRecord::Base
where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
end
+
+ def filter_by_state(milestones, state)
+ case state
+ when 'closed' then milestones.closed
+ when 'all' then milestones
+ else milestones.active
+ end
+ end
end
def self.reference_prefix
@@ -138,6 +161,8 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid, full: false)
+ return if is_group_milestone?
+
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
@@ -152,6 +177,10 @@ class Milestone < ActiveRecord::Base
id
end
+ def for_display
+ self
+ end
+
def can_be_closed?
active? && issues.opened.count.zero?
end
@@ -164,8 +193,45 @@ class Milestone < ActiveRecord::Base
write_attribute(:title, sanitize_title(value)) if value.present?
end
+ def safe_title
+ title.to_slug.normalize.to_s
+ end
+
+ def parent
+ group || project
+ end
+
+ def is_group_milestone?
+ group_id.present?
+ end
+
+ def is_project_milestone?
+ project_id.present?
+ end
+
private
+ # Milestone titles must be unique across project milestones and group milestones
+ def uniqueness_of_title
+ if project
+ relation = Milestone.for_projects_and_groups([project_id], [project.group&.id])
+ elsif group
+ project_ids = group.projects.map(&:id)
+ relation = Milestone.for_projects_and_groups(project_ids, [group.id])
+ end
+
+ title_exists = relation.find_by_title(title)
+ errors.add(:title, "already being used for another group or project milestone.") if title_exists
+ end
+
+ # Milestone should be either a project milestone or a group milestone
+ def milestone_type_check
+ if group_id && project_id
+ field = project_id_changed? ? :project_id : :group_id
+ errors.add(field, "milestone should belong either to a project or a group.")
+ end
+ end
+
def milestone_format_reference(format = :iid)
raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 583d4fb5244..15713fc5f6d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,5 +1,5 @@
class Namespace < ActiveRecord::Base
- acts_as_paranoid
+ acts_as_paranoid without_default_scope: true
include CacheMarkdownField
include Sortable
@@ -15,13 +15,13 @@ class Namespace < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
- has_many :projects, dependent: :destroy
+ has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
has_many :children, class_name: "Namespace", foreign_key: :parent_id
- has_one :chat_team, dependent: :destroy
+ has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
@@ -219,6 +219,12 @@ class Namespace < ActiveRecord::Base
parent.present?
end
+ def soft_delete_without_removing_associations
+ # We can't use paranoia's `#destroy` since this will hard-delete projects.
+ # Project uses `pending_delete` instead of the acts_as_paranoia gem.
+ self.deleted_at = Time.now
+ end
+
private
def repository_storage_paths
diff --git a/app/models/note.rb b/app/models/note.rb
index ca6999427c0..3d39047d32f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -46,8 +46,8 @@ class Note < ActiveRecord::Base
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
- has_many :todos, dependent: :destroy
- has_many :events, as: :target, dependent: :destroy
+ has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata
delegate :gfm_reference, :local_reference, to: :noteable
@@ -330,8 +330,7 @@ class Note < ActiveRecord::Base
def expire_etag_cache
return unless for_issue?
- key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
- noteable.project.namespace,
+ key = Gitlab::Routing.url_helpers.project_noteable_notes_path(
noteable.project,
target_type: noteable_type.underscore,
target_id: noteable.id
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index b0df7aeb323..81844b1e2ca 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base
# pending delete).
#
scope :for_projects, -> do
- includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil })
+ includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil, pending_delete: true })
end
EMAIL_EVENTS = [
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 6e13f9b2089..654be927ed8 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
- serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user
diff --git a/app/models/project.rb b/app/models/project.rb
index 1176bec8873..d5760164663 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -59,6 +59,7 @@ class Project < ActiveRecord::Base
update_column(:last_repository_updated_at, self.created_at)
end
+ before_destroy :remove_private_deploy_keys
after_destroy :remove_pages
# update visibility_level of forks
@@ -80,96 +81,108 @@ class Project < ActiveRecord::Base
belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
- has_many :boards, before_add: :validate_board_limit, dependent: :destroy
+ has_many :boards, before_add: :validate_board_limit
# Project services
- has_one :campfire_service, dependent: :destroy
- has_one :drone_ci_service, dependent: :destroy
- has_one :emails_on_push_service, dependent: :destroy
- has_one :pipelines_email_service, dependent: :destroy
- has_one :irker_service, dependent: :destroy
- has_one :pivotaltracker_service, dependent: :destroy
- has_one :hipchat_service, dependent: :destroy
- has_one :flowdock_service, dependent: :destroy
- has_one :assembla_service, dependent: :destroy
- has_one :asana_service, dependent: :destroy
- has_one :gemnasium_service, dependent: :destroy
- has_one :mattermost_slash_commands_service, dependent: :destroy
- has_one :mattermost_service, dependent: :destroy
- has_one :slack_slash_commands_service, dependent: :destroy
- has_one :slack_service, dependent: :destroy
- has_one :buildkite_service, dependent: :destroy
- has_one :bamboo_service, dependent: :destroy
- has_one :teamcity_service, dependent: :destroy
- has_one :pushover_service, dependent: :destroy
- has_one :jira_service, dependent: :destroy
- has_one :redmine_service, dependent: :destroy
- has_one :custom_issue_tracker_service, dependent: :destroy
- has_one :bugzilla_service, dependent: :destroy
- has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
- has_one :external_wiki_service, dependent: :destroy
- has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
- has_one :prometheus_service, dependent: :destroy, inverse_of: :project
- has_one :mock_ci_service, dependent: :destroy
- has_one :mock_deployment_service, dependent: :destroy
- has_one :mock_monitoring_service, dependent: :destroy
- has_one :microsoft_teams_service, dependent: :destroy
-
- has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+ has_one :campfire_service
+ has_one :drone_ci_service
+ has_one :emails_on_push_service
+ has_one :pipelines_email_service
+ has_one :irker_service
+ has_one :pivotaltracker_service
+ has_one :hipchat_service
+ has_one :flowdock_service
+ has_one :assembla_service
+ has_one :asana_service
+ has_one :gemnasium_service
+ has_one :mattermost_slash_commands_service
+ has_one :mattermost_service
+ has_one :slack_slash_commands_service
+ has_one :slack_service
+ has_one :buildkite_service
+ has_one :bamboo_service
+ has_one :teamcity_service
+ has_one :pushover_service
+ has_one :jira_service
+ has_one :redmine_service
+ has_one :custom_issue_tracker_service
+ has_one :bugzilla_service
+ has_one :gitlab_issue_tracker_service, inverse_of: :project
+ has_one :external_wiki_service
+ has_one :kubernetes_service, inverse_of: :project
+ has_one :prometheus_service, inverse_of: :project
+ has_one :mock_ci_service
+ has_one :mock_deployment_service
+ has_one :mock_monitoring_service
+ has_one :microsoft_teams_service
+
+ has_one :forked_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
# Merge Requests for target project should be removed with it
- has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
- has_many :issues, dependent: :destroy
- has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
- has_many :services, dependent: :destroy
- has_many :events, dependent: :destroy
- has_many :milestones, dependent: :destroy
- has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
- has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
- has_many :protected_branches, dependent: :destroy
- has_many :protected_tags, dependent: :destroy
+ has_many :merge_requests, foreign_key: 'target_project_id'
+ has_many :issues
+ has_many :labels, class_name: 'ProjectLabel'
+ has_many :services
+ has_many :events
+ has_many :milestones
+ has_many :notes
+ has_many :snippets, class_name: 'ProjectSnippet'
+ has_many :hooks, class_name: 'ProjectHook'
+ has_many :protected_branches
+ has_many :protected_tags
has_many :project_authorizations
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
- has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
+ has_many :project_members, -> { where(requested_at: nil) },
+ as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
alias_method :members, :project_members
has_many :users, through: :project_members
- has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember'
+ has_many :requesters, -> { where.not(requested_at: nil) },
+ as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- has_many :deploy_keys_projects, dependent: :destroy
+ has_many :deploy_keys_projects
has_many :deploy_keys, through: :deploy_keys_projects
- has_many :users_star_projects, dependent: :destroy
+ has_many :users_star_projects
has_many :starrers, through: :users_star_projects, source: :user
- has_many :releases, dependent: :destroy
- has_many :lfs_objects_projects, dependent: :destroy
+ has_many :releases
+ has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :lfs_objects, through: :lfs_objects_projects
- has_many :project_group_links, dependent: :destroy
+ has_many :project_group_links
has_many :invited_groups, through: :project_group_links, source: :group
- has_many :pages_domains, dependent: :destroy
- has_many :todos, dependent: :destroy
- has_many :notification_settings, dependent: :destroy, as: :source
-
- has_one :import_data, dependent: :delete, class_name: 'ProjectImportData'
- has_one :project_feature, dependent: :destroy
- has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
- has_many :container_repositories, dependent: :destroy
-
- has_many :commit_statuses, dependent: :destroy
- has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline'
- has_many :builds, class_name: 'Ci::Build' # the builds are created from the commit_statuses
- has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
+ has_many :pages_domains
+ has_many :todos
+ has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
+ has_one :import_data, class_name: 'ProjectImportData'
+ has_one :project_feature
+ has_one :statistics, class_name: 'ProjectStatistics'
+
+ # Container repositories need to remove data from the container registry,
+ # which is not managed by the DB. Hence we're still using dependent: :destroy
+ # here.
+ has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
+ has_many :commit_statuses
+ has_many :pipelines, class_name: 'Ci::Pipeline'
+
+ # Ci::Build objects store data on the file system such as artifact files and
+ # build traces. Currently there's no efficient way of removing this data in
+ # bulk that doesn't involve loading the rows into memory. As a result we're
+ # still using `dependent: :destroy` here.
+ has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, class_name: 'Ci::Variable'
- has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
- has_many :environments, dependent: :destroy
- has_many :deployments, dependent: :destroy
- has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule'
+ has_many :triggers, class_name: 'Ci::Trigger'
+ has_many :environments
+ has_many :deployments
+ has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -186,6 +199,11 @@ class Project < ActiveRecord::Base
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
+ validates :ci_config_path,
+ format: { without: /\.{2}/,
+ message: 'cannot include directory traversal.' },
+ length: { maximum: 255 },
+ allow_blank: true
validates :name,
presence: true,
length: { maximum: 255 },
@@ -219,12 +237,11 @@ class Project < ActiveRecord::Base
before_save :ensure_runners_token
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes
- default_scope { where(pending_delete: false) }
-
- scope :with_deleted, -> { unscope(where: :pending_delete) }
+ scope :pending_delete, -> { where(pending_delete: true) }
+ scope :without_deleted, -> { where(pending_delete: false) }
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
@@ -352,7 +369,16 @@ class Project < ActiveRecord::Base
after_transition started: :finished do |project, _|
project.reset_cache_and_import_attrs
- project.perform_housekeeping
+
+ if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
+ project.run_after_commit do
+ begin
+ Projects::HousekeepingService.new(project).execute
+ rescue Projects::HousekeepingService::LeaseTaken => e
+ Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}")
+ end
+ end
+ end
end
end
@@ -513,22 +539,16 @@ class Project < ActiveRecord::Base
remove_import_data
end
- def perform_housekeeping
- return unless repo_exists?
-
- run_after_commit do
- begin
- Projects::HousekeepingService.new(self).execute
- rescue Projects::HousekeepingService::LeaseTaken => e
- Rails.logger.info("Could not perform housekeeping for project #{self.path_with_namespace} (#{self.id}): #{e}")
- end
- end
- end
-
+ # This method is overriden in EE::Project model
def remove_import_data
import_data&.destroy
end
+ def ci_config_path=(value)
+ # Strip all leading slashes so that //foo -> foo
+ super(value&.sub(%r{\A/+}, '')&.delete("\0"))
+ end
+
def import_url=(value)
return super(value) unless Gitlab::UrlSanitizer.valid?(value)
@@ -683,7 +703,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Routing.url_helpers.project_url(self)
end
def new_issue_address(author)
@@ -735,8 +755,8 @@ class Project < ActiveRecord::Base
end
end
- def issue_reference_pattern
- issues_tracker.reference_pattern
+ def external_issue_reference_pattern
+ external_issue_tracker.class.reference_pattern
end
def default_issues_tracker?
@@ -781,10 +801,12 @@ class Project < ActiveRecord::Base
update_column(:has_external_wiki, services.external_wikis.any?)
end
- def find_or_initialize_services
+ def find_or_initialize_services(exceptions: [])
services_templates = Service.where(template: true)
- Service.available_services_names.map do |service_name|
+ available_services_names = Service.available_services_names - exceptions
+
+ available_services_names.map do |service_name|
service = find_service(services, service_name)
if service
@@ -823,7 +845,7 @@ class Project < ActiveRecord::Base
end
def ci_service
- @ci_service ||= ci_services.reorder(nil).find_by(active: true)
+ @ci_service ||= ci_services.find_by(active: true)
end
def deployment_services
@@ -831,7 +853,7 @@ class Project < ActiveRecord::Base
end
def deployment_service
- @deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
+ @deployment_service ||= deployment_services.find_by(active: true)
end
def monitoring_services
@@ -839,7 +861,7 @@ class Project < ActiveRecord::Base
end
def monitoring_service
- @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
+ @monitoring_service ||= monitoring_services.find_by(active: true)
end
def jira_tracker?
@@ -859,7 +881,7 @@ class Project < ActiveRecord::Base
def avatar_url(**args)
# We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git)
+ avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git)
end
# For compatibility with old code
@@ -971,6 +993,7 @@ class Project < ActiveRecord::Base
begin
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
+ expires_full_path_cache
@old_path_with_namespace = old_path_with_namespace
@@ -1022,7 +1045,8 @@ class Project < ActiveRecord::Base
namespace: namespace.name,
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
- default_branch: default_branch
+ default_branch: default_branch,
+ ci_config_path: ci_config_path
}
# Backward compatibility
@@ -1081,19 +1105,23 @@ class Project < ActiveRecord::Base
merge_requests.where(source_project_id: self.id)
end
- def create_repository
+ def create_repository(force: false)
# Forked import is handled asynchronously
- unless forked?
- if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
- repository.after_create
- true
- else
- errors.add(:base, 'Failed to create repository via gitlab-shell')
- false
- end
+ return if forked? && !force
+
+ if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
+ repository.after_create
+ true
+ else
+ errors.add(:base, 'Failed to create repository via gitlab-shell')
+ false
end
end
+ def ensure_repository
+ create_repository(force: true) unless repository_exists?
+ end
+
def repository_exists?
!!repository.exists?
end
@@ -1232,7 +1260,13 @@ class Project < ActiveRecord::Base
File.join(pages_path, 'public')
end
+ def remove_private_deploy_keys
+ deploy_keys.where(public: false).delete_all
+ end
+
def remove_pages
+ ::Projects::UpdatePagesConfigurationService.new(self).execute
+
# 1. We rename pages to temporary directory
# 2. We wait 5 minutes, due to NFS caching
# 3. We asynchronously remove pages with force
@@ -1318,7 +1352,8 @@ class Project < ActiveRecord::Base
variables
end
- def secret_variables_for(ref)
+ def secret_variables_for(ref:, environment: nil)
+ # EE would use the environment
if protected_for?(ref)
variables
else
@@ -1456,7 +1491,7 @@ class Project < ActiveRecord::Base
def pending_delete_twin
return false unless path
- Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace)
+ Project.pending_delete.find_by_full_path(path_with_namespace)
end
##
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 48edd0738ee..c8fabb16dc1 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -51,8 +51,11 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
- access_level = public_send(ProjectFeature.access_level_attribute(feature))
- get_permission(user, access_level)
+ get_permission(user, access_level(feature))
+ end
+
+ def access_level(feature)
+ public_send(ProjectFeature.access_level_attribute(feature))
end
def builds_enabled?
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index e3cafd4d1c6..37730474324 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base
insecure_mode: true,
algorithm: 'aes-256-cbc'
- serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :data, JSON # rubocop:disable Cop/ActiveRecordSerialize
validates :project, presence: true
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index ad4eb9536e1..420102875a5 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,5 +1,5 @@
class GitlabIssueTrackerService < IssueTrackerService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
@@ -12,26 +12,26 @@ class GitlabIssueTrackerService < IssueTrackerService
end
def project_url
- namespace_project_issues_url(project.namespace, project)
+ project_issues_url(project)
end
def new_issue_url
- new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project)
+ new_project_issue_url(project)
end
def issue_url(iid)
- namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid)
+ project_issue_url(project, id: iid)
end
def project_path
- namespace_project_issues_path(project.namespace, project)
+ project_issues_path(project)
end
def new_issue_path
- new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)
+ new_project_issue_path(project)
end
def issue_path(iid)
- namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)
+ project_issue_path(project, id: iid)
end
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index ff138b9066d..1fa4cd4db30 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -5,7 +5,10 @@ class IssueTrackerService < Service
# Pattern used to extract links from comments
# Override this method on services that uses different patterns
- def reference_pattern
+ # This pattern does not support cross-project references
+ # The other code assumes that this pattern is a superset of all
+ # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
+ def self.reference_pattern
@reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
end
@@ -18,7 +21,7 @@ class IssueTrackerService < Service
end
def project_path
- project_url
+ read_attribute(:project_url)
end
def new_issue_path
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2450fb43212..5498a2e17b2 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,5 +1,5 @@
class JiraService < IssueTrackerService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
@@ -18,7 +18,7 @@ class JiraService < IssueTrackerService
end
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
- def reference_pattern
+ def self.reference_pattern
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
@@ -152,8 +152,8 @@ class JiraService < IssueTrackerService
url: resource_url(user_path(author))
},
project: {
- name: self.project.path_with_namespace,
- url: resource_url(namespace_project_path(project.namespace, self.project))
+ name: project.path_with_namespace,
+ url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper
},
entity: {
name: noteable_type.humanize.downcase,
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 48e7802c557..62f7c057c5b 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -96,10 +96,13 @@ class KubernetesService < DeploymentService
end
def predefined_variables
+ config = YAML.dump(kubeconfig)
+
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }
+ { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
+ { key: 'KUBECONFIG', value: config, public: false, file: true }
]
if ca_pem.present?
@@ -135,6 +138,14 @@ class KubernetesService < DeploymentService
private
+ def kubeconfig
+ to_kubeconfig(
+ url: api_url,
+ namespace: actual_namespace,
+ token: token,
+ ca_pem: ca_pem)
+ end
+
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 4592cb747a0..eb4da68bb7e 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -5,7 +5,7 @@ class SlashCommandsService < Service
prop_accessor :token
- has_many :chat_names, foreign_key: :service_id, dependent: :destroy
+ has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
def valid_token?(token)
self.respond_to?(:token) &&
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f38fbda7839..dfca0031af8 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -31,7 +31,7 @@ class ProjectWiki
end
def web_url
- Gitlab::Routing.url_helpers.namespace_project_wiki_url(@project.namespace, @project, :home)
+ Gitlab::Routing.url_helpers.project_wiki_url(@project, :home)
end
def url_to_repo
@@ -63,6 +63,10 @@ class ProjectWiki
!!repository.exists?
end
+ def has_home_page?
+ !!find_page('home')
+ end
+
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
@@ -149,6 +153,10 @@ class ProjectWiki
wiki
end
+ def ensure_repository
+ create_repo! unless repository_exists?
+ end
+
def hook_attrs
{
web_url: web_url,
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c67475357d9..10b429c707e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -605,22 +605,6 @@ class Repository
end
end
- # Returns url for submodule
- #
- # Ex.
- # @repository.submodule_url_for('master', 'rack')
- # # => git@localhost:rack.git
- #
- def submodule_url_for(ref, path)
- if submodules(ref).any?
- submodule = submodules(ref)[path]
-
- if submodule
- submodule['url']
- end
- end
- end
-
def last_commit_for_path(sha, path)
sha = last_commit_id_for_path(sha, path)
commit(sha)
@@ -947,7 +931,7 @@ class Repository
def is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
-
+
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id)
@@ -1094,8 +1078,8 @@ class Repository
blob_data_at(sha, '.gitlab/route-map.yml')
end
- def gitlab_ci_yml_for(sha)
- blob_data_at(sha, '.gitlab-ci.yml')
+ def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
+ blob_data_at(sha, path)
end
private
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index edde7bedbab..298569cb7a6 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,5 +1,5 @@
class SentNotification < ActiveRecord::Base
- serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :project
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/service.rb b/app/models/service.rb
index 6a0b0a5c522..6b64079215f 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,7 +2,7 @@
# and implement a set of methods
class Service < ActiveRecord::Base
include Sortable
- serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
default_value_for :active, false
default_value_for :push_events, true
@@ -51,6 +51,14 @@ class Service < ActiveRecord::Base
active
end
+ def show_active_box?
+ true
+ end
+
+ def editable?
+ true
+ end
+
def template?
template
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 54014df43b0..09d5ff46618 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -30,16 +30,14 @@ class Snippet < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :project
- has_many :notes, as: :noteable, dependent: :destroy
+ has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
validates :file_name,
- length: { maximum: 255 },
- format: { with: Gitlab::Regex.file_name_regex,
- message: Gitlab::Regex.file_name_regex_message }
+ length: { maximum: 255 }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
diff --git a/app/models/user.rb b/app/models/user.rb
index 6dd1b1415d6..4b01c2f19f0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -11,6 +11,8 @@ class User < ActiveRecord::Base
include CaseSensitivity
include TokenAuthenticatable
include IgnorableColumn
+ include FeatureGate
+ include CreatedAtFilterable
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -40,7 +42,7 @@ class User < ActiveRecord::Base
otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
- serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
@@ -66,24 +68,24 @@ class User < ActiveRecord::Base
#
# Namespace for personal projects
- has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true
+ has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
has_many :keys, -> do
type = Key.arel_table[:type]
where(type.not_eq('DeployKey').or(type.eq(nil)))
- end, dependent: :destroy
- has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy
+ end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :emails, dependent: :destroy
- has_many :personal_access_tokens, dependent: :destroy
- has_many :identities, dependent: :destroy, autosave: true
- has_many :u2f_registrations, dependent: :destroy
- has_many :chat_names, dependent: :destroy
+ has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
+ has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Groups
- has_many :members, dependent: :destroy
- has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember'
+ has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
@@ -91,35 +93,35 @@ class User < ActiveRecord::Base
# Projects
has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects
- has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy
+ has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
- has_many :users_star_projects, dependent: :destroy
+ has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :starred_projects, through: :users_star_projects, source: :project
has_many :project_authorizations
has_many :authorized_projects, through: :project_authorizations, source: :project
- has_many :snippets, dependent: :destroy, foreign_key: :author_id
- has_many :notes, dependent: :destroy, foreign_key: :author_id
- has_many :issues, dependent: :destroy, foreign_key: :author_id
- has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
- has_many :events, dependent: :destroy, foreign_key: :author_id
- has_many :subscriptions, dependent: :destroy
+ has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
- has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
- has_one :abuse_report, dependent: :destroy, foreign_key: :user_id
- has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport"
- has_many :spam_logs, dependent: :destroy
- has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
- has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline'
- has_many :todos, dependent: :destroy
- has_many :notification_settings, dependent: :destroy
- has_many :award_emoji, dependent: :destroy
- has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
+ has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent
+ has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent
+ has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent
+ has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issue_assignees
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
- has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+ has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
#
# Validations
@@ -210,7 +212,7 @@ class User < ActiveRecord::Base
end
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes
scope :admins, -> { where(admin: true) }
@@ -299,11 +301,20 @@ class User < ActiveRecord::Base
table = arel_table
pattern = "%#{query}%"
+ order = <<~SQL
+ CASE
+ WHEN users.name = %{query} THEN 0
+ WHEN users.username = %{query} THEN 1
+ WHEN users.email = %{query} THEN 2
+ ELSE 3
+ END
+ SQL
+
where(
table[:name].matches(pattern)
.or(table[:email].matches(pattern))
.or(table[:username].matches(pattern))
- )
+ ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
end
# searches user by given pattern
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 623424c63e0..a605a3457c8 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -1,127 +1,20 @@
-class BasePolicy
- class RuleSet
- attr_reader :can_set, :cannot_set
- def initialize(can_set, cannot_set)
- @can_set = can_set
- @cannot_set = cannot_set
- end
+require_dependency 'declarative_policy'
- delegate :size, to: :to_set
+class BasePolicy < DeclarativePolicy::Base
+ include Gitlab::CurrentSettings
- def self.empty
- new(Set.new, Set.new)
- end
+ desc "User is an instance admin"
+ with_options scope: :user, score: 0
+ condition(:admin) { @user&.admin? }
- def self.none
- empty.freeze
- end
+ with_options scope: :user, score: 0
+ condition(:external_user) { @user.nil? || @user.external? }
- def can?(ability)
- @can_set.include?(ability) && !@cannot_set.include?(ability)
- end
+ with_options scope: :user, score: 0
+ condition(:can_create_group) { @user&.can_create_group }
- def include?(ability)
- can?(ability)
- end
-
- def to_set
- @can_set - @cannot_set
- end
-
- def merge(other)
- @can_set.merge(other.can_set)
- @cannot_set.merge(other.cannot_set)
- end
-
- def can!(*abilities)
- @can_set.merge(abilities)
- end
-
- def cannot!(*abilities)
- @cannot_set.merge(abilities)
- end
-
- def freeze
- @can_set.freeze
- @cannot_set.freeze
- super
- end
- end
-
- def self.abilities(user, subject)
- new(user, subject).abilities
- end
-
- def self.class_for(subject)
- return GlobalPolicy if subject == :global
- raise ArgumentError, 'no policy for nil' if subject.nil?
-
- if subject.class.try(:presenter?)
- subject = subject.subject
- end
-
- subject.class.ancestors.each do |klass|
- next unless klass.name
-
- begin
- policy_class = "#{klass.name}Policy".constantize
-
- # NOTE: the < operator here tests whether policy_class
- # inherits from BasePolicy
- return policy_class if policy_class < BasePolicy
- rescue NameError
- nil
- end
- end
-
- raise "no policy for #{subject.class.name}"
- end
-
- attr_reader :user, :subject
- def initialize(user, subject)
- @user = user
- @subject = subject
- end
-
- def abilities
- return RuleSet.none if @user && @user.blocked?
- return anonymous_abilities if @user.nil?
- collect_rules { rules }
- end
-
- def anonymous_abilities
- collect_rules { anonymous_rules }
- end
-
- def anonymous_rules
- rules
- end
-
- def rules
- raise NotImplementedError
- end
-
- def delegate!(new_subject)
- @rule_set.merge(Ability.allowed(@user, new_subject))
- end
-
- def can?(rule)
- @rule_set.can?(rule)
- end
-
- def can!(*rules)
- @rule_set.can!(*rules)
- end
-
- def cannot!(*rules)
- @rule_set.cannot!(*rules)
- end
-
- private
-
- def collect_rules(&b)
- @rule_set = RuleSet.empty
- yield
- @rule_set
+ desc "The application is restricted from public visibility"
+ condition(:restricted_public_level, scope: :global) do
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 2d7405dc240..a886efc1360 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,29 +1,13 @@
module Ci
class BuildPolicy < CommitStatusPolicy
- alias_method :build, :subject
-
- def rules
- super
-
- # If we can't read build we should also not have that
- # ability when looking at this in context of commit_status
- %w[read create update admin].each do |rule|
- cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
- end
-
- if can?(:update_build) && protected_action?
- cannot! :update_build
- end
- end
-
- private
-
- def protected_action?
- return false unless build.action?
+ condition(:protected_action) do
+ next false unless @subject.action?
!::Gitlab::UserAccess
- .new(user, project: build.project)
- .can_merge_to_branch?(build.ref)
+ .new(@user, project: @subject.project)
+ .can_merge_to_branch?(@subject.ref)
end
+
+ rule { protected_action }.prevent :update_build
end
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index 10aa2d3e72a..a2dde95dbc8 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -1,7 +1,5 @@
module Ci
class PipelinePolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
end
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index 1877e89bb23..6b7598e1821 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -1,4 +1,14 @@
module Ci
class PipelineSchedulePolicy < PipelinePolicy
+ alias_method :pipeline_schedule, :subject
+
+ condition(:owner_of_schedule) do
+ can?(:developer_access) && pipeline_schedule.owned_by?(@user)
+ end
+
+ rule { can?(:master_access) | owner_of_schedule }.policy do
+ enable :update_pipeline_schedule
+ enable :admin_pipeline_schedule
+ end
end
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 416d93ffe63..7dff8470e23 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -1,13 +1,16 @@
module Ci
class RunnerPolicy < BasePolicy
- def rules
- return unless @user
+ with_options scope: :subject, score: 0
+ condition(:shared) { @subject.is_shared? }
- can! :assign_runner if @user.admin?
+ with_options scope: :subject, score: 0
+ condition(:locked, scope: :subject) { @subject.locked? }
- return if @subject.is_shared? || @subject.locked?
+ condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) }
- can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
- end
+ rule { anonymous }.prevent_all
+ rule { admin | authorized_runner }.enable :assign_runner
+ rule { ~admin & shared }.prevent :assign_runner
+ rule { ~admin & locked }.prevent :assign_runner
end
end
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index c90c9ac0583..5592ac30812 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -1,13 +1,16 @@
module Ci
class TriggerPolicy < BasePolicy
- def rules
- delegate! @subject.project
-
- if can?(:admin_build)
- can! :admin_trigger if @subject.owner.blank? ||
- @subject.owner == @user
- can! :manage_trigger
- end
- end
+ delegate { @subject.project }
+
+ with_options scope: :subject, score: 0
+ condition(:legacy) { @subject.legacy? }
+
+ with_score 0
+ condition(:is_owner) { @user && @subject.owner_id == @user.id }
+
+ rule { ~can?(:admin_build) }.prevent :admin_trigger
+ rule { legacy | is_owner }.enable :admin_trigger
+
+ rule { can?(:admin_build) }.enable :manage_trigger
end
end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
index 593df738328..24b2a4cc7fd 100644
--- a/app/policies/commit_status_policy.rb
+++ b/app/policies/commit_status_policy.rb
@@ -1,5 +1,7 @@
class CommitStatusPolicy < BasePolicy
- def rules
- delegate! @subject.project
+ delegate { @subject.project }
+
+ %w[read create update admin].each do |action|
+ rule { ~can?(:"#{action}_commit_status") }.prevent :"#{action}_build"
end
end
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index ebab213e6be..62a22a59be6 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -1,11 +1,11 @@
class DeployKeyPolicy < BasePolicy
- def rules
- return unless @user
+ with_options scope: :subject, score: 0
+ condition(:private_deploy_key) { @subject.private? }
- can! :update_deploy_key if @user.admin?
+ condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
- if @subject.private? && @user.project_deploy_keys.exists?(id: @subject.id)
- can! :update_deploy_key
- end
- end
+ rule { anonymous }.prevent_all
+
+ rule { admin }.enable :update_deploy_key
+ rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
index 163d070ff90..62b63b9f87b 100644
--- a/app/policies/deployment_policy.rb
+++ b/app/policies/deployment_policy.rb
@@ -1,5 +1,3 @@
class DeploymentPolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
index 2fa15e64562..375a5535359 100644
--- a/app/policies/environment_policy.rb
+++ b/app/policies/environment_policy.rb
@@ -1,17 +1,9 @@
class EnvironmentPolicy < BasePolicy
- alias_method :environment, :subject
+ delegate { @subject.project }
- def rules
- delegate! environment.project
-
- if can?(:create_deployment) && environment.stop_action?
- can! :stop_environment if can_play_stop_action?
- end
+ condition(:stop_action_allowed) do
+ @subject.stop_action? && can?(:update_build, @subject.stop_action)
end
- private
-
- def can_play_stop_action?
- Ability.allowed?(user, :update_build, environment.stop_action)
- end
+ rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment
end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
index d9e28bd107a..e031b38078c 100644
--- a/app/policies/external_issue_policy.rb
+++ b/app/policies/external_issue_policy.rb
@@ -1,5 +1,3 @@
class ExternalIssuePolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 2683aaad981..55eefa76d3f 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -1,16 +1,50 @@
class GlobalPolicy < BasePolicy
- def rules
- return unless @user
-
- can! :create_group if @user.can_create_group
- can! :read_users_list
-
- unless @user.blocked? || @user.internal?
- can! :log_in unless @user.access_locked?
- can! :access_api
- can! :access_git
- can! :receive_notifications
- can! :use_quick_actions
- end
+ desc "User is blocked"
+ with_options scope: :user, score: 0
+ condition(:blocked) { @user.blocked? }
+
+ desc "User is an internal user"
+ with_options scope: :user, score: 0
+ condition(:internal) { @user.internal? }
+
+ desc "User's access has been locked"
+ with_options scope: :user, score: 0
+ condition(:access_locked) { @user.access_locked? }
+
+ rule { anonymous }.policy do
+ prevent :log_in
+ prevent :access_api
+ prevent :access_git
+ prevent :receive_notifications
+ prevent :use_quick_actions
+ prevent :create_group
+ end
+
+ rule { default }.policy do
+ enable :log_in
+ enable :access_api
+ enable :access_git
+ enable :receive_notifications
+ enable :use_quick_actions
+ end
+
+ rule { blocked | internal }.policy do
+ prevent :log_in
+ prevent :access_api
+ prevent :access_git
+ prevent :receive_notifications
+ prevent :use_quick_actions
+ end
+
+ rule { can_create_group }.policy do
+ enable :create_group
+ end
+
+ rule { access_locked }.policy do
+ prevent :log_in
+ end
+
+ rule { ~restricted_public_level }.policy do
+ enable :read_users_list
end
end
diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb
index 7b34aa182eb..e3dd3296699 100644
--- a/app/policies/group_label_policy.rb
+++ b/app/policies/group_label_policy.rb
@@ -1,5 +1,3 @@
class GroupLabelPolicy < BasePolicy
- def rules
- delegate! @subject.group
- end
+ delegate { @subject.group }
end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
index 5a3fe814b77..23dd0d7cd23 100644
--- a/app/policies/group_member_policy.rb
+++ b/app/policies/group_member_policy.rb
@@ -1,25 +1,22 @@
class GroupMemberPolicy < BasePolicy
- def rules
- return unless @user
+ delegate :group
- target_user = @subject.user
- group = @subject.group
+ with_scope :subject
+ condition(:last_owner) { @subject.group.last_owner?(@subject.user) }
- return if group.last_owner?(target_user)
+ desc "Membership is users' own"
+ with_score 0
+ condition(:is_target_user) { @user && @subject.user_id == @user.id }
- can_manage = Ability.allowed?(@user, :admin_group_member, group)
+ rule { anonymous }.prevent_all
+ rule { last_owner }.prevent_all
- if can_manage
- can! :update_group_member
- can! :destroy_group_member
- elsif @user == target_user
- can! :destroy_group_member
- end
-
- additional_rules!
+ rule { can?(:admin_group_member) }.policy do
+ enable :update_group_member
+ enable :destroy_group_member
end
- def additional_rules!
- # This is meant to be overriden in EE
+ rule { is_target_user }.policy do
+ enable :destroy_group_member
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index fb07298c6c2..6defab75fce 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,50 +1,60 @@
class GroupPolicy < BasePolicy
- def rules
- can! :read_group if @subject.public?
- return unless @user
-
- globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
- access_level = @subject.max_member_access_for_user(@user)
- owner = access_level >= GroupMember::OWNER
- master = access_level >= GroupMember::MASTER
- reporter = access_level >= GroupMember::REPORTER
-
- can_read = false
- can_read ||= globally_viewable
- can_read ||= access_level >= GroupMember::GUEST
- can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
- can! :read_group if can_read
-
- if reporter
- can! :admin_label
- end
-
- # Only group masters and group owners can create new projects
- if master
- can! :create_projects
- can! :admin_milestones
- end
-
- # Only group owner and administrators can admin group
- if owner
- can! :admin_group
- can! :admin_namespace
- can! :admin_group_member
- can! :change_visibility_level
- can! :create_subgroup if @user.can_create_group
- end
-
- if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS
- can! :request_access
- end
- end
+ desc "Group is public"
+ with_options scope: :subject, score: 0
+ condition(:public_group) { @subject.public? }
+
+ with_score 0
+ condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
+
+ condition(:has_access) { access_level != GroupMember::NO_ACCESS }
- def can_read_group?
- return true if @subject.public?
- return true if @user.admin?
- return true if @subject.internal? && !@user.external?
- return true if @subject.users.include?(@user)
+ condition(:guest) { access_level >= GroupMember::GUEST }
+ condition(:owner) { access_level >= GroupMember::OWNER }
+ condition(:master) { access_level >= GroupMember::MASTER }
+ condition(:reporter) { access_level >= GroupMember::REPORTER }
+ condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end
+
+ with_options scope: :subject, score: 0
+ condition(:request_access_enabled) { @subject.request_access_enabled }
+
+ rule { public_group } .enable :read_group
+ rule { logged_in_viewable }.enable :read_group
+ rule { guest } .enable :read_group
+ rule { admin } .enable :read_group
+ rule { has_projects } .enable :read_group
+
+ rule { reporter }.enable :admin_label
+
+ rule { master }.policy do
+ enable :create_projects
+ enable :admin_milestones
+ enable :admin_pipeline
+ enable :admin_build
+ end
+
+ rule { owner }.policy do
+ enable :admin_group
+ enable :admin_namespace
+ enable :admin_group_member
+ enable :change_visibility_level
+ end
+
+ rule { owner & can_create_group }.enable :create_subgroup
+
+ rule { public_group | logged_in_viewable }.enable :view_globally
+
+ rule { default }.enable(:request_access)
+
+ rule { ~request_access_enabled }.prevent :request_access
+ rule { ~can?(:view_globally) }.prevent :request_access
+ rule { has_access }.prevent :request_access
+
+ def access_level
+ return GroupMember::NO_ACCESS if @user.nil?
+
+ @access_level ||= @subject.max_member_access_for_user(@user)
+ end
end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 9501e499507..daf6fa9e18a 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -1,14 +1,15 @@
class IssuablePolicy < BasePolicy
- def action_name
- @subject.class.name.underscore
- end
+ delegate { @subject.project }
- def rules
- if @user && @subject.assignee_or_author?(@user)
- can! :"read_#{action_name}"
- can! :"update_#{action_name}"
- end
+ desc "User is the assignee or author"
+ condition(:assignee_or_author) do
+ @user && @subject.assignee_or_author?(@user)
+ end
- delegate! @subject.project
+ rule { assignee_or_author }.policy do
+ enable :read_issue
+ enable :update_issue
+ enable :read_merge_request
+ enable :update_merge_request
end
end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 88f3179c6ff..bd2d417b2a8 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
- def issue
- @subject
+ desc "User can read confidential issues"
+ condition(:can_read_confidential) do
+ @user && IssueCollection.new([@subject]).visible_to(@user).any?
end
- def rules
- super
+ desc "Issue is confidential"
+ condition(:confidential, scope: :subject) { @subject.confidential? }
- if @subject.confidential? && !can_read_confidential?
- cannot! :read_issue
- cannot! :update_issue
- cannot! :admin_issue
- end
- end
-
- private
-
- def can_read_confidential?
- return false unless @user
-
- IssueCollection.new([@subject]).visible_to(@user).any?
+ rule { confidential & ~can_read_confidential }.policy do
+ prevent :read_issue
+ prevent :update_issue
+ prevent :admin_issue
end
end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index 29bb357e00a..85b67f0a237 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -1,10 +1,10 @@
class NamespacePolicy < BasePolicy
- def rules
- return unless @user
+ rule { anonymous }.prevent_all
- if @subject.owner == @user || @user.admin?
- can! :create_projects
- can! :admin_namespace
- end
+ condition(:owner) { @subject.owner == @user }
+
+ rule { owner | admin }.policy do
+ enable :create_projects
+ enable :admin_namespace
end
end
diff --git a/app/policies/nil_policy.rb b/app/policies/nil_policy.rb
new file mode 100644
index 00000000000..13f46ba60f0
--- /dev/null
+++ b/app/policies/nil_policy.rb
@@ -0,0 +1,3 @@
+class NilPolicy < BasePolicy
+ rule { default }.prevent_all
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index 5326061bd07..20cd51cfb99 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,19 +1,24 @@
class NotePolicy < BasePolicy
- def rules
- delegate! @subject.project
+ delegate { @subject.project }
- return unless @user
+ condition(:is_author) { @user && @subject.author == @user }
+ condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
+ condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
- if @subject.author == @user
- can! :read_note
- can! :update_note
- can! :admin_note
- can! :resolve_note
- end
+ condition(:editable, scope: :subject) { @subject.editable? }
- if @subject.for_merge_request? &&
- @subject.noteable.author == @user
- can! :resolve_note
- end
+ rule { ~editable | anonymous }.prevent :edit_note
+ rule { is_author | admin }.enable :edit_note
+ rule { can?(:master_access) }.enable :edit_note
+
+ rule { is_author }.policy do
+ enable :read_note
+ enable :update_note
+ enable :admin_note
+ enable :resolve_note
+ end
+
+ rule { for_merge_request & is_noteable_author }.policy do
+ enable :resolve_note
end
end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index e1e5336da8c..cac0530b9f7 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -1,27 +1,28 @@
class PersonalSnippetPolicy < BasePolicy
- def rules
- can! :read_personal_snippet if @subject.public?
- return unless @user
+ condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:is_author) { @user && @subject.author == @user }
+ condition(:internal_snippet, scope: :subject) { @subject.internal? }
- if @subject.public?
- can! :comment_personal_snippet
- end
+ rule { public_snippet }.policy do
+ enable :read_personal_snippet
+ enable :comment_personal_snippet
+ end
- if @subject.author == @user
- can! :read_personal_snippet
- can! :update_personal_snippet
- can! :destroy_personal_snippet
- can! :admin_personal_snippet
- can! :comment_personal_snippet
- end
+ rule { is_author }.policy do
+ enable :read_personal_snippet
+ enable :update_personal_snippet
+ enable :destroy_personal_snippet
+ enable :admin_personal_snippet
+ enable :comment_personal_snippet
+ end
- unless @user.external?
- can! :create_personal_snippet
- end
+ rule { ~anonymous }.enable :create_personal_snippet
+ rule { external_user }.prevent :create_personal_snippet
- if @subject.internal? && !@user.external?
- can! :read_personal_snippet
- can! :comment_personal_snippet
- end
+ rule { internal_snippet & ~external_user }.policy do
+ enable :read_personal_snippet
+ enable :comment_personal_snippet
end
+
+ rule { anonymous }.prevent :comment_personal_snippet
end
diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb
index b12b4c5166b..2d0f021118b 100644
--- a/app/policies/project_label_policy.rb
+++ b/app/policies/project_label_policy.rb
@@ -1,5 +1,3 @@
class ProjectLabelPolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
index 1c038dddd4b..9aedb620be9 100644
--- a/app/policies/project_member_policy.rb
+++ b/app/policies/project_member_policy.rb
@@ -1,22 +1,16 @@
class ProjectMemberPolicy < BasePolicy
- def rules
- # anonymous users have no abilities here
- return unless @user
+ delegate { @subject.project }
- target_user = @subject.user
- project = @subject.project
+ condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
+ condition(:target_is_self) { @user && @subject.user == @user }
- return if target_user == project.owner
+ rule { anonymous }.prevent_all
+ rule { target_is_owner }.prevent_all
- can_manage = Ability.allowed?(@user, :admin_project_member, project)
-
- if can_manage
- can! :update_project_member
- can! :destroy_project_member
- end
-
- if @user == target_user
- can! :destroy_project_member
- end
+ rule { can?(:admin_project_member) }.policy do
+ enable :update_project_member
+ enable :destroy_project_member
end
+
+ rule { target_is_self }.enable :destroy_project_member
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 47518dddb61..323131c0f7e 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -1,297 +1,351 @@
class ProjectPolicy < BasePolicy
- def rules
- team_access!(user)
+ def self.create_read_update_admin(name)
+ [
+ :"create_#{name}",
+ :"read_#{name}",
+ :"update_#{name}",
+ :"admin_#{name}"
+ ]
+ end
- owner_access! if user.admin? || owner?
- team_member_owner_access! if owner?
+ desc "User is a project owner"
+ condition :owner do
+ @user && project.owner == @user || (project.group && project.group.has_owner?(@user))
+ end
- if project.public? || (project.internal? && !user.external?)
- guest_access!
- public_access!
- can! :request_access if access_requestable?
- end
+ desc "Project has public builds enabled"
+ condition(:public_builds, scope: :subject) { project.public_builds? }
+
+ # For guest access we use #is_team_member? so we can use
+ # project.members, which gets cached in subject scope.
+ # This is safe because team_access_level is guaranteed
+ # by ProjectAuthorization's validation to be at minimum
+ # GUEST
+ desc "User has guest access"
+ condition(:guest) { is_team_member? }
- archived_access! if project.archived?
+ desc "User has reporter access"
+ condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
- disabled_features!
+ desc "User has developer access"
+ condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
+
+ desc "User has master access"
+ condition(:master) { team_access_level >= Gitlab::Access::MASTER }
+
+ desc "Project is public"
+ condition(:public_project, scope: :subject) { project.public? }
+
+ desc "Project is visible to internal users"
+ condition(:internal_access) do
+ project.internal? && !user.external?
end
- def project
- @subject
+ desc "User is a member of the group"
+ condition(:group_member, scope: :subject) { project_group_member? }
+
+ desc "Project is archived"
+ condition(:archived, scope: :subject) { project.archived? }
+
+ condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
+
+ desc "Container registry is disabled"
+ condition(:container_registry_disabled, scope: :subject) do
+ !project.container_registry_enabled
end
- def owner?
- return @owner if defined?(@owner)
-
- @owner = project.owner == user ||
- (project.group && project.group.has_owner?(user))
- end
-
- def guest_access!
- can! :read_project
- can! :read_board
- can! :read_list
- can! :read_wiki
- can! :read_issue
- can! :read_label
- can! :read_milestone
- can! :read_project_snippet
- can! :read_project_member
- can! :read_note
- can! :create_project
- can! :create_issue
- can! :create_note
- can! :upload_file
- can! :read_cycle_analytics
-
- if project.public_builds?
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_build
- end
+ desc "Project has an external wiki"
+ condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
+
+ desc "Project has request access enabled"
+ condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
+
+ features = %w[
+ merge_requests
+ issues
+ repository
+ snippets
+ wiki
+ builds
+ ]
+
+ features.each do |f|
+ # these are scored high because they are unlikely
+ desc "Project has #{f} disabled"
+ condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
end
- def reporter_access!
- can! :download_code
- can! :download_wiki_code
- can! :fork_project
- can! :create_project_snippet
- can! :update_issue
- can! :admin_issue
- can! :admin_label
- can! :admin_list
- can! :read_commit_status
- can! :read_build
- can! :read_container_image
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_environment
- can! :read_deployment
- can! :read_merge_request
- end
-
- # Permissions given when an user is team member of a project
- def team_member_reporter_access!
- can! :build_download_code
- can! :build_read_container_image
- end
-
- def developer_access!
- can! :admin_merge_request
- can! :update_merge_request
- can! :create_commit_status
- can! :update_commit_status
- can! :create_build
- can! :update_build
- can! :create_pipeline
- can! :update_pipeline
- can! :create_pipeline_schedule
- can! :update_pipeline_schedule
- can! :create_merge_request
- can! :create_wiki
- can! :push_code
- can! :resolve_note
- can! :create_container_image
- can! :update_container_image
- can! :create_environment
- can! :create_deployment
- end
-
- def master_access!
- can! :delete_protected_branch
- can! :update_project_snippet
- can! :update_environment
- can! :update_deployment
- can! :admin_milestone
- can! :admin_project_snippet
- can! :admin_project_member
- can! :admin_note
- can! :admin_wiki
- can! :admin_project
- can! :admin_commit_status
- can! :admin_build
- can! :admin_container_image
- can! :admin_pipeline
- can! :admin_pipeline_schedule
- can! :admin_environment
- can! :admin_deployment
- can! :admin_pages
- can! :read_pages
- can! :update_pages
- end
-
- def public_access!
- can! :download_code
- can! :fork_project
- can! :read_commit_status
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_container_image
- can! :build_download_code
- can! :build_read_container_image
- can! :read_merge_request
- end
-
- def owner_access!
- guest_access!
- reporter_access!
- developer_access!
- master_access!
- can! :change_namespace
- can! :change_visibility_level
- can! :rename_project
- can! :remove_project
- can! :archive_project
- can! :remove_fork_project
- can! :destroy_merge_request
- can! :destroy_issue
- can! :remove_pages
- end
-
- def team_member_owner_access!
- team_member_reporter_access!
- end
-
- # Push abilities on the users team role
- def team_access!(user)
- access = project.team.max_member_access(user.id)
-
- return if access < Gitlab::Access::GUEST
- guest_access!
-
- return if access < Gitlab::Access::REPORTER
- reporter_access!
- team_member_reporter_access!
-
- return if access < Gitlab::Access::DEVELOPER
- developer_access!
-
- return if access < Gitlab::Access::MASTER
- master_access!
- end
-
- def archived_access!
- cannot! :create_merge_request
- cannot! :push_code
- cannot! :delete_protected_branch
- cannot! :update_merge_request
- cannot! :admin_merge_request
- end
-
- def disabled_features!
- repository_enabled = project.feature_available?(:repository, user)
-
- block_issues_abilities
-
- unless project.feature_available?(:merge_requests, user) && repository_enabled
- cannot!(*named_abilities(:merge_request))
- end
+ rule { guest }.enable :guest_access
+ rule { reporter }.enable :reporter_access
+ rule { developer }.enable :developer_access
+ rule { master }.enable :master_access
+
+ rule { owner | admin }.policy do
+ enable :guest_access
+ enable :reporter_access
+ enable :developer_access
+ enable :master_access
+
+ enable :change_namespace
+ enable :change_visibility_level
+ enable :rename_project
+ enable :remove_project
+ enable :archive_project
+ enable :remove_fork_project
+ enable :destroy_merge_request
+ enable :destroy_issue
+ enable :remove_pages
+ end
- unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
- cannot!(*named_abilities(:label))
- cannot!(*named_abilities(:milestone))
- end
+ rule { owner | reporter }.policy do
+ enable :build_download_code
+ enable :build_read_container_image
+ end
- unless project.feature_available?(:snippets, user)
- cannot!(*named_abilities(:project_snippet))
- end
+ rule { can?(:guest_access) }.policy do
+ enable :read_project
+ enable :read_board
+ enable :read_list
+ enable :read_wiki
+ enable :read_issue
+ enable :read_label
+ enable :read_milestone
+ enable :read_project_snippet
+ enable :read_project_member
+ enable :read_note
+ enable :create_project
+ enable :create_issue
+ enable :create_note
+ enable :upload_file
+ enable :read_cycle_analytics
+ enable :read_project_snippet
+ end
- unless project.feature_available?(:wiki, user) || project.has_external_wiki?
- cannot!(*named_abilities(:wiki))
- cannot!(:download_wiki_code)
- end
+ rule { can?(:reporter_access) }.policy do
+ enable :download_code
+ enable :download_wiki_code
+ enable :fork_project
+ enable :create_project_snippet
+ enable :update_issue
+ enable :admin_issue
+ enable :admin_label
+ enable :admin_list
+ enable :read_commit_status
+ enable :read_build
+ enable :read_container_image
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ enable :read_environment
+ enable :read_deployment
+ enable :read_merge_request
+ end
- unless project.feature_available?(:builds, user) && repository_enabled
- cannot!(*named_abilities(:build))
- cannot!(*named_abilities(:pipeline) - [:read_pipeline])
- cannot!(*named_abilities(:pipeline_schedule))
- cannot!(*named_abilities(:environment))
- cannot!(*named_abilities(:deployment))
- end
+ rule { (~anonymous & public_project) | internal_access }.policy do
+ enable :public_user_access
+ end
- unless repository_enabled
- cannot! :push_code
- cannot! :delete_protected_branch
- cannot! :download_code
- cannot! :fork_project
- cannot! :read_commit_status
- end
+ rule { can?(:public_user_access) }.policy do
+ enable :guest_access
+ enable :request_access
+ end
- unless project.container_registry_enabled
- cannot!(*named_abilities(:container_image))
- end
+ rule { owner | admin | guest | group_member }.prevent :request_access
+ rule { ~request_access_enabled }.prevent :request_access
+
+ rule { can?(:developer_access) }.policy do
+ enable :admin_merge_request
+ enable :update_merge_request
+ enable :create_commit_status
+ enable :update_commit_status
+ enable :create_build
+ enable :update_build
+ enable :create_pipeline
+ enable :update_pipeline
+ enable :create_pipeline_schedule
+ enable :create_merge_request
+ enable :create_wiki
+ enable :push_code
+ enable :resolve_note
+ enable :create_container_image
+ enable :update_container_image
+ enable :create_environment
+ enable :create_deployment
end
- def anonymous_rules
- return unless project.public?
+ rule { can?(:master_access) }.policy do
+ enable :delete_protected_branch
+ enable :update_project_snippet
+ enable :update_environment
+ enable :update_deployment
+ enable :admin_milestone
+ enable :admin_project_snippet
+ enable :admin_project_member
+ enable :admin_note
+ enable :admin_wiki
+ enable :admin_project
+ enable :admin_commit_status
+ enable :admin_build
+ enable :admin_container_image
+ enable :admin_pipeline
+ enable :admin_environment
+ enable :admin_deployment
+ enable :admin_pages
+ enable :read_pages
+ enable :update_pages
+ end
- base_readonly_access!
+ rule { can?(:public_user_access) }.policy do
+ enable :public_access
- # Allow to read builds by anonymous user if guests are allowed
- can! :read_build if project.public_builds?
+ enable :fork_project
+ enable :build_download_code
+ enable :build_read_container_image
+ end
- disabled_features!
+ rule { archived }.policy do
+ prevent :create_merge_request
+ prevent :push_code
+ prevent :delete_protected_branch
+ prevent :update_merge_request
+ prevent :admin_merge_request
end
- def block_issues_abilities
- unless project.feature_available?(:issues, user)
- cannot! :read_issue if project.default_issues_tracker?
- cannot! :create_issue
- cannot! :update_issue
- cannot! :admin_issue
- end
+ rule { merge_requests_disabled | repository_disabled }.policy do
+ prevent(*create_read_update_admin(:merge_request))
end
- def named_abilities(name)
- [
- :"read_#{name}",
- :"create_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
+ rule { issues_disabled & merge_requests_disabled }.policy do
+ prevent(*create_read_update_admin(:label))
+ prevent(*create_read_update_admin(:milestone))
+ end
+
+ rule { snippets_disabled }.policy do
+ prevent(*create_read_update_admin(:project_snippet))
+ end
+
+ rule { wiki_disabled & ~has_external_wiki }.policy do
+ prevent(*create_read_update_admin(:wiki))
+ prevent(:download_wiki_code)
+ end
+
+ rule { builds_disabled | repository_disabled }.policy do
+ prevent(*create_read_update_admin(:build))
+ prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
+ prevent(*create_read_update_admin(:pipeline_schedule))
+ prevent(*create_read_update_admin(:environment))
+ prevent(*create_read_update_admin(:deployment))
+ end
+
+ rule { repository_disabled }.policy do
+ prevent :push_code
+ prevent :push_code_to_protected_branches
+ prevent :download_code
+ prevent :fork_project
+ prevent :read_commit_status
+ end
+
+ rule { container_registry_disabled }.policy do
+ prevent(*create_read_update_admin(:container_image))
+ end
+
+ rule { anonymous & ~public_project }.prevent_all
+ rule { public_project }.enable(:public_access)
+
+ rule { can?(:public_access) }.policy do
+ enable :read_project
+ enable :read_board
+ enable :read_list
+ enable :read_wiki
+ enable :read_label
+ enable :read_milestone
+ enable :read_project_snippet
+ enable :read_project_member
+ enable :read_merge_request
+ enable :read_note
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ enable :read_commit_status
+ enable :read_container_image
+ enable :download_code
+ enable :download_wiki_code
+ enable :read_cycle_analytics
+
+ # NOTE: may be overridden by IssuePolicy
+ enable :read_issue
+ end
+
+ rule { public_builds }.policy do
+ enable :read_build
+ end
+
+ rule { public_builds & can?(:guest_access) }.policy do
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ end
+
+ rule { issues_disabled }.policy do
+ prevent :create_issue
+ prevent :update_issue
+ prevent :admin_issue
+ end
+
+ rule { issues_disabled & default_issues_tracker }.policy do
+ prevent :read_issue
end
private
- def project_group_member?(user)
+ def is_team_member?
+ return false if @user.nil?
+
+ greedy_load_subject = false
+
+ # when scoping by subject, we want to be greedy
+ # and load *all* the members with one query.
+ greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject
+
+ # in this case we're likely to have loaded #members already
+ # anyways, and #member? would fail with an error
+ greedy_load_subject ||= !@user.persisted?
+
+ if greedy_load_subject
+ project.team.members.include?(user)
+ else
+ # otherwise we just make a specific query for
+ # this particular user.
+ team_access_level >= Gitlab::Access::GUEST
+ end
+ end
+
+ def project_group_member?
+ return false if @user.nil?
+
project.group &&
(
- project.group.members_with_parents.exists?(user_id: user.id) ||
- project.group.requesters.exists?(user_id: user.id)
+ project.group.members_with_parents.exists?(user_id: @user.id) ||
+ project.group.requesters.exists?(user_id: @user.id)
)
end
- def access_requestable?
- project.request_access_enabled &&
- !owner? &&
- !user.admin? &&
- !project.team.member?(user) &&
- !project_group_member?(user)
- end
-
- # A base set of abilities for read-only users, which
- # is then augmented as necessary for anonymous and other
- # read-only users.
- def base_readonly_access!
- can! :read_project
- can! :read_board
- can! :read_list
- can! :read_wiki
- can! :read_label
- can! :read_milestone
- can! :read_project_snippet
- can! :read_project_member
- can! :read_merge_request
- can! :read_note
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_commit_status
- can! :read_container_image
- can! :download_code
- can! :download_wiki_code
- can! :read_cycle_analytics
+ def team_access_level
+ return -1 if @user.nil?
- # NOTE: may be overridden by IssuePolicy
- can! :read_issue
+ # NOTE: max_member_access has its own cache
+ project.team.max_member_access(@user.id)
+ end
+
+ def feature_available?(feature)
+ case project.project_feature.access_level(feature)
+ when ProjectFeature::DISABLED
+ false
+ when ProjectFeature::PRIVATE
+ guest? || admin?
+ else
+ true
+ end
+ end
+
+ def project
+ @subject
end
end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index bc5c4f32f79..dd270643bbf 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -1,25 +1,45 @@
class ProjectSnippetPolicy < BasePolicy
- def rules
- # We have to check both project feature visibility and a snippet visibility and take the stricter one
- # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
- return unless @subject.project.feature_available?(:snippets, @user)
- return unless Ability.allowed?(@user, :read_project, @subject.project)
-
- can! :read_project_snippet if @subject.public?
- return unless @user
-
- if @user && (@subject.author == @user || @user.admin?)
- can! :read_project_snippet
- can! :update_project_snippet
- can! :admin_project_snippet
- end
-
- if @subject.internal? && !@user.external?
- can! :read_project_snippet
- end
-
- if @subject.project.team.member?(@user)
- can! :read_project_snippet
- end
+ delegate :project
+
+ desc "Snippet is public"
+ condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:private_snippet, scope: :subject) { @subject.private? }
+ condition(:public_project, scope: :subject) { @subject.project.public? }
+
+ condition(:is_author) { @user && @subject.author == @user }
+
+ condition(:internal, scope: :subject) { @subject.internal? }
+
+ # We have to check both project feature visibility and a snippet visibility and take the stricter one
+ # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
+ rule { ~can?(:read_project) }.policy do
+ prevent :read_project_snippet
+ prevent :update_project_snippet
+ prevent :admin_project_snippet
+ end
+
+ # we have to use this complicated prevent because the delegated project policy
+ # is overly greedy in allowing :read_project_snippet, since it doesn't have any
+ # information about the snippet. However, :read_project_snippet on the *project*
+ # is used to hide/show various snippet-related controls, so we can't just move
+ # all of the handling here.
+ rule do
+ all?(private_snippet | (internal & external_user),
+ ~project.guest,
+ ~admin,
+ ~is_author)
+ end.prevent :read_project_snippet
+
+ rule { internal & ~is_author & ~admin }.policy do
+ prevent :update_project_snippet
+ prevent :admin_project_snippet
+ end
+
+ rule { public_snippet }.enable :read_project_snippet
+
+ rule { is_author | admin }.policy do
+ enable :read_project_snippet
+ enable :update_project_snippet
+ enable :admin_project_snippet
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 229846e368c..0905ddd9b38 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -1,19 +1,13 @@
class UserPolicy < BasePolicy
- include Gitlab::CurrentSettings
+ desc "The current user is the user in question"
+ condition(:user_is_self, score: 0) { @subject == @user }
- def rules
- can! :read_user if @user || !restricted_public_level?
+ desc "This is the ghost user"
+ condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
- if @user
- if @user.admin? || @subject == @user
- can! :destroy_user
- end
+ rule { ~restricted_public_level }.enable :read_user
+ rule { ~anonymous }.enable :read_user
- cannot! :destroy_user if @subject.ghost?
- end
- end
-
- def restricted_public_level?
- current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
- end
+ rule { user_is_self | admin }.enable :destroy_user
+ rule { subject_ghost }.prevent :destroy_user
end
diff --git a/app/presenters/ci/group_variable_presenter.rb b/app/presenters/ci/group_variable_presenter.rb
new file mode 100644
index 00000000000..81fea106a5c
--- /dev/null
+++ b/app/presenters/ci/group_variable_presenter.rb
@@ -0,0 +1,25 @@
+module Ci
+ class GroupVariablePresenter < Gitlab::View::Presenter::Delegated
+ presents :variable
+
+ def placeholder
+ 'GROUP_VARIABLE'
+ end
+
+ def form_path
+ if variable.persisted?
+ group_variable_path(group, variable)
+ else
+ group_variables_path(group)
+ end
+ end
+
+ def edit_path
+ group_variable_path(group, variable)
+ end
+
+ def delete_path
+ group_variable_path(group, variable)
+ end
+ end
+end
diff --git a/app/presenters/ci/variable_presenter.rb b/app/presenters/ci/variable_presenter.rb
new file mode 100644
index 00000000000..5d7998393a6
--- /dev/null
+++ b/app/presenters/ci/variable_presenter.rb
@@ -0,0 +1,25 @@
+module Ci
+ class VariablePresenter < Gitlab::View::Presenter::Delegated
+ presents :variable
+
+ def placeholder
+ 'PROJECT_VARIABLE'
+ end
+
+ def form_path
+ if variable.persisted?
+ project_variable_path(project, variable)
+ else
+ project_variables_path(project)
+ end
+ end
+
+ def edit_path
+ project_variable_path(project, variable)
+ end
+
+ def delete_path
+ project_variable_path(project, variable)
+ end
+ end
+end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 8bf35953d29..6ba1d3165e9 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -20,30 +20,25 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def cancel_merge_when_pipeline_succeeds_path
if can_cancel_merge_when_pipeline_succeeds?(current_user)
- cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(
- project.namespace,
- project,
- merge_request)
+ cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
end
end
def create_issue_to_resolve_discussions_path
if can?(current_user, :create_issue, project) && project.issues_enabled?
- new_namespace_project_issue_path(project.namespace,
- project,
- merge_request_to_resolve_discussions_of: iid)
+ new_project_issue_path(project, merge_request_to_resolve_discussions_of: iid)
end
end
def remove_wip_path
if can?(current_user, :update_merge_request, merge_request.project)
- remove_wip_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ remove_wip_project_merge_request_path(project, merge_request)
end
end
def merge_path
if can_be_merged_by?(current_user)
- merge_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ merge_project_merge_request_path(project, merge_request)
end
end
@@ -55,7 +50,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
notice_now: edit_in_new_fork_notice_now
}
- namespace_project_forks_path(merge_request.project.namespace, merge_request.project,
+ project_forks_path(merge_request.project,
namespace_key: current_user.namespace.id,
continue: continue_params)
end
@@ -69,7 +64,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
notice_now: edit_in_new_fork_notice_now
}
- namespace_project_forks_path(project.namespace, project,
+ project_forks_path(project,
namespace_key: current_user.namespace.id,
continue: continue_params)
end
@@ -77,19 +72,19 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def conflict_resolution_path
if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
- conflicts_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ conflicts_project_merge_request_path(project, merge_request)
end
end
def target_branch_commits_path
if target_branch_exists?
- namespace_project_commits_path(project.namespace, project, target_branch)
+ project_commits_path(project, target_branch)
end
end
def source_branch_path
if source_branch_exists?
- namespace_project_branch_path(source_project.namespace, source_project, source_branch)
+ project_branch_path(source_project, source_branch)
end
end
@@ -99,7 +94,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
if source_branch_exists?
namespace = link_to(namespace, project_path(source_project))
- branch = link_to(branch, namespace_project_commits_path(source_project.namespace, source_project, source_branch))
+ branch = link_to(branch, project_commits_path(source_project, source_branch))
end
if for_fork?
@@ -136,7 +131,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
merge_request: merge_request,
closes_issues: closing_issues
).assignable_issues
- path = assign_related_issues_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ path = assign_related_issues_project_merge_request_path(project, merge_request)
if issues.present?
pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
index 301b718d060..f2d76a8ad81 100644
--- a/app/serializers/build_action_entity.rb
+++ b/app/serializers/build_action_entity.rb
@@ -6,10 +6,7 @@ class BuildActionEntity < Grape::Entity
end
expose :path do |build|
- play_namespace_project_job_path(
- build.project.namespace,
- build.project,
- build)
+ play_project_job_path(build.project, build)
end
expose :playable?, as: :playable
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index cb55c98f7c6..6e0e33bc09b 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -9,24 +9,15 @@ class BuildArtifactEntity < Grape::Entity
expose :artifacts_expire_at, as: :expire_at
expose :path do |job|
- download_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ download_project_job_artifacts_path(project, job)
end
expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job|
- keep_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ keep_project_job_artifacts_path(project, job)
end
expose :browse_path do |job|
- browse_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ browse_project_job_artifacts_path(project, job)
end
private
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index eeb5399aa8b..20f9938f038 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -7,7 +7,7 @@ class BuildDetailsEntity < JobEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
- erase_namespace_project_job_path(project.namespace, project, build)
+ erase_project_job_path(project, build)
end
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
@@ -16,23 +16,23 @@ class BuildDetailsEntity < JobEntity
end
expose :path do |build|
- namespace_project_merge_request_path(project.namespace, project, build.merge_request)
+ project_merge_request_path(project, build.merge_request)
end
end
expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build|
- new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options)
+ new_project_issue_path(project, issue: build_failed_issue_options)
end
expose :raw_path do |build|
- raw_namespace_project_job_path(project.namespace, project, build)
+ raw_project_job_path(project, build)
end
private
def build_failed_issue_options
{ title: "Build Failed ##{build.id}",
- description: namespace_project_job_path(project.namespace, project, build) }
+ description: project_job_path(project, build) }
end
def current_user
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index 31763955f97..e4e9d8ef90a 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -8,16 +8,10 @@ class CommitEntity < API::Entities::RepoCommit
end
expose :commit_url do |commit|
- namespace_project_commit_url(
- request.project.namespace,
- request.project,
- commit)
+ project_commit_url(request.project, commit)
end
expose :commit_path do |commit|
- namespace_project_commit_path(
- request.project.namespace,
- request.project,
- commit)
+ project_commit_path(request.project, commit)
end
end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index e493c9162fd..241c689bccd 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -11,10 +11,7 @@ class DeploymentEntity < Grape::Entity
end
expose :ref_path do |deployment|
- namespace_project_tree_path(
- deployment.project.namespace,
- deployment.project,
- id: deployment.ref)
+ project_tree_path(deployment.project, id: deployment.ref)
end
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 4e8a3c67b21..dcaccc3007d 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -10,32 +10,20 @@ class EnvironmentEntity < Grape::Entity
expose :stop_action?
expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
- metrics_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ metrics_project_environment_path(environment.project, environment)
end
expose :environment_path do |environment|
- namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ project_environment_path(environment.project, environment)
end
expose :stop_path do |environment|
- stop_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ stop_project_environment_path(environment.project, environment)
end
expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
can?(request.current_user, :admin_environment, environment.project) &&
- terminal_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ terminal_project_environment_path(environment.project, environment)
end
expose :created_at, :updated_at
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 35df95549b7..c189a4992da 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -11,6 +11,6 @@ class IssueEntity < IssuableEntity
expose :labels, using: LabelEntity
expose :web_url do |issue|
- namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ project_issue_path(issue.project, issue)
end
end
diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb
index ad565654342..4452161051e 100644
--- a/app/serializers/label_entity.rb
+++ b/app/serializers/label_entity.rb
@@ -1,5 +1,6 @@
class LabelEntity < Grape::Entity
- expose :id
+ expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) }
+
expose :title
expose :color
expose :description
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 7bb981041cc..7ec2dbd0efe 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -99,9 +99,7 @@ class MergeRequestEntity < IssuableEntity
expose :new_blob_path do |merge_request|
if can?(current_user, :push_code, merge_request.project)
- namespace_project_new_blob_path(merge_request.project.namespace,
- merge_request.project,
- merge_request.source_branch)
+ project_new_blob_path(merge_request.project, merge_request.source_branch)
end
end
@@ -134,30 +132,19 @@ class MergeRequestEntity < IssuableEntity
end
expose :email_patches_path do |merge_request|
- namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- format: :patch)
+ project_merge_request_path(merge_request.project, merge_request, format: :patch)
end
expose :plain_diff_path do |merge_request|
- namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- format: :diff)
+ project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
expose :status_path do |merge_request|
- namespace_project_merge_request_path(merge_request.target_project.namespace,
- merge_request.target_project,
- merge_request,
- format: :json)
+ project_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :ci_environments_status_path do |merge_request|
- ci_environments_status_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request)
+ ci_environments_status_project_merge_request_path(merge_request.project, merge_request)
end
expose :merge_commit_message_with_description do |merge_request|
@@ -173,9 +160,7 @@ class MergeRequestEntity < IssuableEntity
end
expose :commit_change_content_path do |merge_request|
- commit_change_content_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request)
+ commit_change_content_project_merge_request_path(merge_request.project, merge_request)
end
private
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 6d1fd9d459f..c4f000b0ca3 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -10,10 +10,7 @@ class PipelineEntity < Grape::Entity
expose :created_at, :updated_at
expose :path do |pipeline|
- namespace_project_pipeline_path(
- pipeline.project.namespace,
- pipeline.project,
- pipeline)
+ project_pipeline_path(pipeline.project, pipeline)
end
expose :flags do
@@ -48,15 +45,11 @@ class PipelineEntity < Grape::Entity
expose :commit, using: CommitEntity
expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
- retry_namespace_project_pipeline_path(pipeline.project.namespace,
- pipeline.project,
- pipeline.id)
+ retry_project_pipeline_path(pipeline.project, pipeline)
end
expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline|
- cancel_namespace_project_pipeline_path(pipeline.project.namespace,
- pipeline.project,
- pipeline.id)
+ cancel_project_pipeline_path(pipeline.project, pipeline)
end
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index a471a7e6a88..dc283ba3e7a 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -5,7 +5,7 @@ class ProjectEntity < Grape::Entity
expose :name
expose :full_path do |project|
- namespace_project_path(project.namespace, project)
+ project_path(project)
end
expose :full_name do |project|
diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb
index ed7dacc2dbd..e9999a36d8a 100644
--- a/app/serializers/runner_entity.rb
+++ b/app/serializers/runner_entity.rb
@@ -5,7 +5,7 @@ class RunnerEntity < Grape::Entity
expose :edit_path,
if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner|
- edit_namespace_project_runner_path(project.namespace, project, runner)
+ edit_project_runner_path(project, runner)
end
private
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index cee0089056f..4523b15152e 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -14,16 +14,14 @@ class StageEntity < Grape::Entity
expose :detailed_status, as: :status, with: StatusEntity
expose :path do |stage|
- namespace_project_pipeline_path(
- stage.pipeline.project.namespace,
+ project_pipeline_path(
stage.pipeline.project,
stage.pipeline,
anchor: stage.name)
end
expose :dropdown_path do |stage|
- stage_namespace_project_pipeline_path(
- stage.pipeline.project.namespace,
+ stage_project_pipeline_path(
stage.pipeline.project,
stage.pipeline,
stage: stage.name,
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index b2a543daa00..9c00ea789ec 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -5,10 +5,11 @@ class AccessTokenValidationService
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
- attr_reader :token
+ attr_reader :token, :request
- def initialize(token)
+ def initialize(token, request: nil)
@token = token
+ @request = request
end
def validate(scopes: [])
@@ -27,12 +28,23 @@ class AccessTokenValidationService
end
# True if the token's scope contains any of the passed scopes.
- def include_any_scope?(scopes)
- if scopes.blank?
+ def include_any_scope?(required_scopes)
+ if required_scopes.blank?
true
else
- # Check whether the token is allowed access to any of the required scopes.
- Set.new(scopes).intersection(Set.new(token.scopes)).present?
+ # We're comparing each required_scope against all token scopes, which would
+ # take quadratic time. This consideration is irrelevant here because of the
+ # small number of records involved.
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12300/#note_33689006
+ 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
+ end
end
end
end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 68f6a8619e5..9eedb9e65a2 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -1,19 +1,22 @@
module Boards
class CreateService < BaseService
def execute
- if project.boards.empty?
- create_board!
- else
- project.boards.first
- end
+ create_board! if can_create_board?
end
private
+ def can_create_board?
+ project.boards.size == 0
+ end
+
def create_board!
- board = project.boards.create
- board.lists.create(list_type: :backlog)
- board.lists.create(list_type: :closed)
+ board = project.boards.create(params)
+
+ if board.persisted?
+ board.lists.create(list_type: :backlog)
+ board.lists.create(list_type: :closed)
+ end
board
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 418fa9afd6e..a1d67cbc244 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -3,7 +3,7 @@ module Boards
class ListService < BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
- issues = without_board_labels(issues) unless movable_list?
+ issues = without_board_labels(issues) unless movable_list? || closed_list?
issues = with_list_label(issues) if movable_list?
issues.order_by_position_and_priority
end
@@ -21,7 +21,15 @@ module Boards
end
def movable_list?
- @movable_list ||= list.present? && list.movable?
+ return @movable_list if defined?(@movable_list)
+
+ @movable_list = list.present? && list.movable?
+ end
+
+ def closed_list?
+ return @closed_list if defined?(@closed_list)
+
+ @closed_list = list.present? && list.closed?
end
def filter_params
diff --git a/app/services/chat_names/authorize_user_service.rb b/app/services/chat_names/authorize_user_service.rb
index 321bf3a9205..7256466c9e8 100644
--- a/app/services/chat_names/authorize_user_service.rb
+++ b/app/services/chat_names/authorize_user_service.rb
@@ -1,6 +1,6 @@
module ChatNames
class AuthorizeUserService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
def initialize(service, params)
@service = service
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 942145c4a8c..4f35255fb53 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -33,7 +33,7 @@ module Ci
unless pipeline.config_processor
unless pipeline.ci_yaml_file
- return error('Missing .gitlab-ci.yml file')
+ return error("Missing #{pipeline.ci_yaml_file_path} file")
end
return error(pipeline.yaml_errors, save: save_on_errors)
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index af84d4c7427..b951e8d0c9f 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -54,7 +54,7 @@ module Ci
def builds_for_shared_runner
new_builds.
# don't run projects which have not enabled shared runners and builds
- joins(:project).where(projects: { shared_runners_enabled: true })
+ joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
@@ -66,7 +66,7 @@ module Ci
end
def builds_for_specific_runner
- new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC')
+ new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC')
end
def running_builds_for_shared_runners
diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb
index 3b611588466..5c9e2a16c71 100644
--- a/app/services/delete_merged_branches_service.rb
+++ b/app/services/delete_merged_branches_service.rb
@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names
+ # Prevent deletion of protected branches
+ branches -= project.protected_branches.pluck(:name)
branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch)
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index d222d1e63aa..eab65d09299 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -3,8 +3,8 @@ class GitHooksService
attr_accessor :oldrev, :newrev, :ref
- def execute(user, repo_path, oldrev, newrev, ref)
- @repo_path = repo_path
+ def execute(user, project, oldrev, newrev, ref)
+ @project = project
@user = Gitlab::GlId.gl_id(user)
@oldrev = oldrev
@newrev = newrev
@@ -26,7 +26,7 @@ class GitHooksService
private
def run_hook(name)
- hook = Gitlab::Git::Hook.new(name, @repo_path)
+ hook = Gitlab::Git::Hook.new(name, @project)
hook.trigger(@user, oldrev, newrev, ref)
end
end
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
index ed6ea638235..43636fde0be 100644
--- a/app/services/git_operation_service.rb
+++ b/app/services/git_operation_service.rb
@@ -120,7 +120,7 @@ class GitOperationService
def with_hooks(ref, newrev, oldrev)
GitHooksService.new.execute(
user,
- repository.path_to_repo,
+ repository.project,
oldrev,
newrev,
ref) do |service|
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 497fdb09cdc..80c51cb5a72 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -1,8 +1,7 @@
module Groups
class DestroyService < Groups::BaseService
def async_execute
- # Soft delete via paranoia gem
- group.destroy
+ group.soft_delete_without_removing_associations
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
@@ -10,7 +9,7 @@ module Groups
def execute
group.prepare_for_destroy
- group.projects.with_deleted.each do |project|
+ group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
# Skip repository removal because we remove directory with namespace
# that contain all these repositories
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 8dd0846f3bc..a03a7abfeb1 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -2,8 +2,11 @@ class IssuableBaseService < BaseService
private
def create_milestone_note(issuable)
+ milestone = issuable.milestone
+ return if milestone && milestone.is_group_milestone?
+
SystemNoteService.change_milestone(
- issuable, issuable.project, current_user, issuable.milestone)
+ issuable, issuable.project, current_user, milestone)
end
def create_labels_note(issuable, old_labels)
@@ -89,10 +92,12 @@ class IssuableBaseService < BaseService
milestone_id = params[:milestone_id]
return unless milestone_id
- if milestone_id == IssuableFinder::NONE ||
- project.milestones.find_by(id: milestone_id).nil?
- params[:milestone_id] = ''
- end
+ params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
+
+ milestone =
+ Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
+
+ params[:milestone_id] = '' unless milestone
end
def filter_labels
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 711f4035c55..29def25719d 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -61,8 +61,18 @@ module Issues
end
def cloneable_milestone_id
- @new_project.milestones
- .find_by(title: @old_issue.milestone.try(:title)).try(:id)
+ title = @old_issue.milestone&.title
+ return unless title
+
+ if @new_project.group && can?(current_user, :read_group, @new_project.group)
+ group_id = @new_project.group.id
+ end
+
+ params =
+ { title: title, project_ids: @new_project.id, group_ids: group_id }
+
+ milestones = MilestonesFinder.new(params).execute
+ milestones.first&.id
end
def rewrite_notes
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index f00a33969a8..668a1741736 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -49,13 +49,13 @@ module MergeRequests
def url_for_new_merge_request(branch_name)
merge_request_params = { source_branch: branch_name }
- url = Gitlab::Routing.url_helpers.new_namespace_project_merge_request_url(project.namespace, project, merge_request: merge_request_params)
+ url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
{ branch_name: branch_name, url: url, new_merge_request: true }
end
def url_for_existing_merge_request(merge_request)
target_project = merge_request.target_project
- url = Gitlab::Routing.url_helpers.namespace_project_merge_request_url(target_project.namespace, target_project, merge_request)
+ url = Gitlab::Routing.url_helpers.project_merge_request_url(target_project, merge_request)
{ branch_name: merge_request.source_branch, url: url, new_merge_request: false }
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index b247cb89e5e..bc846e07f24 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -61,8 +61,12 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
- DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
- .execute(merge_request.source_branch)
+ # Verify again that the source branch can be removed, since branch may be protected,
+ # or the source branch may have been updated.
+ if @merge_request.can_remove_source_branch?(branch_deletion_user)
+ DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
+ .execute(merge_request.source_branch)
+ end
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e0e7c43f802..bc4a13cf4bc 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -35,11 +35,12 @@ module MergeRequests
# target branch manually
def close_merge_requests
commit_ids = @commits.map(&:id)
- merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
+ merge_requests = @project.merge_requests.preload(:merge_request_diff).opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:diff_head_commit)
merge_requests = merge_requests.select do |merge_request|
- commit_ids.include?(merge_request.diff_head_sha)
+ commit_ids.include?(merge_request.diff_head_sha) &&
+ merge_request.merge_request_diff.state != 'empty'
end
filter_merge_requests(merge_requests).each do |merge_request|
@@ -68,7 +69,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff(current_user)
else
- mr_commit_ids = merge_request.commits_sha
+ mr_commit_ids = merge_request.commit_shas
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff(current_user) if matches.any?
@@ -128,7 +129,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- mr_commit_ids = Set.new(merge_request.commits_sha)
+ mr_commit_ids = Set.new(merge_request.commit_shas)
new_commits, existing_commits = @commits.partition do |commit|
mr_commit_ids.include?(commit.id)
@@ -144,7 +145,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- commit_shas = merge_request.commits_sha
+ commit_shas = merge_request.commit_shas
wip_commit = @commits.detect do |commit|
commit.work_in_progress? && commit_shas.include?(commit.sha)
diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb
index 176ab9f1ab5..4963601ea8b 100644
--- a/app/services/milestones/base_service.rb
+++ b/app/services/milestones/base_service.rb
@@ -1,4 +1,10 @@
module Milestones
class BaseService < ::BaseService
+ # Parent can either a group or a project
+ attr_accessor :parent, :current_user, :params
+
+ def initialize(parent, user, params = {})
+ @parent, @current_user, @params = parent, user, params.dup
+ end
end
end
diff --git a/app/services/milestones/close_service.rb b/app/services/milestones/close_service.rb
index 608fc49d766..776ec4b287b 100644
--- a/app/services/milestones/close_service.rb
+++ b/app/services/milestones/close_service.rb
@@ -1,7 +1,7 @@
module Milestones
class CloseService < Milestones::BaseService
def execute(milestone)
- if milestone.close
+ if milestone.close && milestone.is_project_milestone?
event_service.close_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index b8e08c9f1eb..aef3124c7e3 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -1,9 +1,9 @@
module Milestones
class CreateService < Milestones::BaseService
def execute
- milestone = project.milestones.new(params)
+ milestone = parent.milestones.new(params)
- if milestone.save
+ if milestone.save && milestone.is_project_milestone?
event_service.open_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb
index e457212508f..600ebcfbecb 100644
--- a/app/services/milestones/destroy_service.rb
+++ b/app/services/milestones/destroy_service.rb
@@ -1,15 +1,17 @@
module Milestones
class DestroyService < Milestones::BaseService
def execute(milestone)
+ return unless milestone.is_project_milestone?
+
Milestone.transaction do
update_params = { milestone: nil }
milestone.issues.each do |issue|
- Issues::UpdateService.new(project, current_user, update_params).execute(issue)
+ Issues::UpdateService.new(parent, current_user, update_params).execute(issue)
end
milestone.merge_requests.each do |merge_request|
- MergeRequests::UpdateService.new(project, current_user, update_params).execute(merge_request)
+ MergeRequests::UpdateService.new(parent, current_user, update_params).execute(merge_request)
end
event_service.destroy_milestone(milestone, current_user)
diff --git a/app/services/milestones/reopen_service.rb b/app/services/milestones/reopen_service.rb
index 573f9ee5c21..5b8b682caaf 100644
--- a/app/services/milestones/reopen_service.rb
+++ b/app/services/milestones/reopen_service.rb
@@ -1,7 +1,7 @@
module Milestones
class ReopenService < Milestones::BaseService
def execute(milestone)
- if milestone.activate
+ if milestone.activate && milestone.is_project_milestone?
event_service.reopen_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb
index ed64847f429..31b441ed476 100644
--- a/app/services/milestones/update_service.rb
+++ b/app/services/milestones/update_service.rb
@@ -5,9 +5,9 @@ module Milestones
case state
when 'activate'
- Milestones::ReopenService.new(project, current_user, {}).execute(milestone)
+ Milestones::ReopenService.new(parent, current_user, {}).execute(milestone)
when 'close'
- Milestones::CloseService.new(project, current_user, {}).execute(milestone)
+ Milestones::CloseService.new(parent, current_user, {}).execute(milestone)
end
if params.present?
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 8d1820bc504..9ac561e4bd2 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -11,7 +11,7 @@ class NotificationRecipientService
def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
custom_action = build_custom_key(action, target)
- recipients = target.participants(current_user)
+ recipients = participants(target, current_user)
recipients = add_project_watchers(recipients)
recipients = add_custom_notifications(recipients, custom_action)
recipients = reject_mention_users(recipients)
@@ -86,12 +86,7 @@ class NotificationRecipientService
mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) }
# Add all users participating in the thread (author, assignee, comment authors)
- recipients =
- if target.respond_to?(:participants)
- target.participants(note.author)
- else
- mentioned_users
- end
+ recipients = participants(target, note.author) || mentioned_users
unless note.for_personal_snippet?
# Merge project watchers
@@ -123,6 +118,14 @@ class NotificationRecipientService
protected
+ # Ensure that if we modify this array, we aren't modifying the memoised
+ # participants on the target.
+ def participants(target, user)
+ return unless target.respond_to?(:participants)
+
+ target.participants(user).dup
+ end
+
# Get project/group users with CUSTOM notification level
def add_custom_notifications(recipients, action)
user_ids = []
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index fd701e33524..4bb98e5cb4e 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -78,6 +78,7 @@ module Projects
Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
project.old_path_with_namespace = @old_path
+ project.expires_full_path_cache
execute_system_hooks
end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index 315c3e16292..f385e426827 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -10,7 +10,7 @@ module Projects
merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
merge_requests.each do |mr|
- MergeRequests::CloseService.new(@project, @current_user).execute(mr)
+ ::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
end
@project.forked_project_link.destroy
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 17cf71cf098..e60b854f916 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -93,10 +93,11 @@ module Projects
end
# Requires UnZip at least 6.00 Info-ZIP.
+ # -qq be (very) quiet
# -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*')
- unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path}))
+ unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 6816b137361..6f82159e6c7 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -92,9 +92,12 @@ module QuickActions
desc 'Assign'
explanation do |users|
- "Assigns #{users.first.to_reference}." if users.any?
+ users = issuable.allows_multiple_assignees? ? users : users.take(1)
+ "Assigns #{users.map(&:to_reference).to_sentence}."
+ end
+ params do
+ issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
- params '@user'
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -104,28 +107,43 @@ module QuickActions
command :assign do |users|
next if users.empty?
- if issuable.is_a?(Issue)
- @updates[:assignee_ids] = [users.last.id]
+ @updates[:assignee_ids] =
+ if issuable.allows_multiple_assignees?
+ issuable.assignees.pluck(:id) + users.map(&:id)
+ else
+ [users.last.id]
+ end
+ end
+
+ desc do
+ if issuable.allows_multiple_assignees?
+ 'Remove all or specific assignee(s)'
else
- @updates[:assignee_id] = users.last.id
+ 'Remove assignee'
end
end
-
- desc 'Remove assignee'
explanation do
- "Removes assignee #{issuable.assignees.first.to_reference}."
+ "Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
+ end
+ params do
+ issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end
condition do
issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
- command :unassign do
- if issuable.is_a?(Issue)
- @updates[:assignee_ids] = []
- else
- @updates[:assignee_id] = nil
- end
+ parse_params do |unassign_param|
+ # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
+ extract_users(unassign_param) if issuable.allows_multiple_assignees?
+ end
+ command :unassign do |users = nil|
+ @updates[:assignee_ids] =
+ if users&.any?
+ issuable.assignees.pluck(:id) - users.map(&:id)
+ else
+ []
+ end
end
desc 'Set milestone'
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0837c07e6aa..da0f21d449a 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -282,7 +282,7 @@ module SystemNoteService
body = "changed this line in"
if version_params = merge_request.version_params_for(diff_refs)
line_code = change_position.line_code(project.repository)
- url = url_helpers.diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, version_params.merge(anchor: line_code))
+ url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code))
body << " [version #{version_index} of the diff](#{url})"
else
@@ -413,7 +413,7 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch)
- link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
+ link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link})"
@@ -630,10 +630,9 @@ module SystemNoteService
def diff_comparison_url(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id
- url_helpers.diffs_namespace_project_merge_request_url(
- project.namespace,
+ url_helpers.diffs_project_merge_request_url(
project,
- merge_request.iid,
+ merge_request,
diff_id: diff_id,
start_sha: oldrev
)
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 673afb8b5b9..9d7237c2fbb 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -35,7 +35,7 @@ module Users
Groups::DestroyService.new(group, current_user).execute
end
- user.personal_projects.with_deleted.each do |project|
+ user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb
new file mode 100644
index 00000000000..8a9d8892e9b
--- /dev/null
+++ b/app/validators/variable_duplicates_validator.rb
@@ -0,0 +1,13 @@
+# VariableDuplicatesValidator
+#
+# This validtor is designed for especially the following condition
+# - Use `accepts_nested_attributes_for :xxx` in a parent model
+# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
+class VariableDuplicatesValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
+ if duplicates.any?
+ record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}")
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index b21d5665970..7f1e13c7989 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -22,7 +22,9 @@
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
- - restricted_level_checkboxes('restricted-visibility-help').each do |level|
+ - checkbox_name = 'application_setting[restricted_visibility_levels][]'
+ = hidden_field_tag(checkbox_name)
+ - restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level|
.checkbox
= level
%span.help-block#restricted-visibility-help
@@ -331,6 +333,22 @@
Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
%fieldset
+ %legend Profiling - Performance Bar
+ %p
+ Enable the Performance Bar for a given group.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :performance_bar_enabled do
+ = f.check_box :performance_bar_enabled
+ Enable the Performance Bar
+ .form-group
+ = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
+
+ %fieldset
%legend Background Jobs
%p
These settings require a restart to take effect.
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3c9f932a225..128b5dc01ab 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -5,182 +5,182 @@
.admin-dashboard.prepend-top-default
.row
.col-md-4
- %h4 Statistics
- %hr
- %p
- Forks
- %span.light.pull-right
- = number_with_delimiter(ForkedProjectLink.count)
- %p
- Issues
- %span.light.pull-right
- = number_with_delimiter(Issue.count)
- %p
- Merge Requests
- %span.light.pull-right
- = number_with_delimiter(MergeRequest.count)
- %p
- Notes
- %span.light.pull-right
- = number_with_delimiter(Note.count)
- %p
- Snippets
- %span.light.pull-right
- = number_with_delimiter(Snippet.count)
- %p
- SSH Keys
- %span.light.pull-right
- = number_with_delimiter(Key.count)
- %p
- Milestones
- %span.light.pull-right
- = number_with_delimiter(Milestone.count)
- %p
- Active Users
- %span.light.pull-right
- = number_with_delimiter(User.active.count)
+ .info-well
+ .well-segment.admin-well
+ %h4 Statistics
+ %p
+ Forks
+ %span.light.pull-right
+ = number_with_delimiter(ForkedProjectLink.count)
+ %p
+ Issues
+ %span.light.pull-right
+ = number_with_delimiter(Issue.count)
+ %p
+ Merge Requests
+ %span.light.pull-right
+ = number_with_delimiter(MergeRequest.count)
+ %p
+ Notes
+ %span.light.pull-right
+ = number_with_delimiter(Note.count)
+ %p
+ Snippets
+ %span.light.pull-right
+ = number_with_delimiter(Snippet.count)
+ %p
+ SSH Keys
+ %span.light.pull-right
+ = number_with_delimiter(Key.count)
+ %p
+ Milestones
+ %span.light.pull-right
+ = number_with_delimiter(Milestone.count)
+ %p
+ Active Users
+ %span.light.pull-right
+ = number_with_delimiter(User.active.count)
.col-md-4
- %h4
- Features
- %hr
- - sign_up = "Sign up"
- %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
- = sign_up
- %span.light.pull-right
- = boolean_to_icon signup_enabled?
- - ldap = "LDAP"
- %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
- = ldap
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.ldap.enabled
- - gravatar = "Gravatar"
- %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
- = gravatar
- %span.light.pull-right
- = boolean_to_icon gravatar_enabled?
- - omniauth = "OmniAuth"
- %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
- = omniauth
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.omniauth.enabled
- - reply_email = "Reply by email"
- %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
- = reply_email
- %span.light.pull-right
- = boolean_to_icon Gitlab::IncomingEmail.enabled?
- - container_reg = "Container Registry"
- %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
- = container_reg
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.registry.enabled
- - gitlab_pages = 'GitLab Pages'
- - gitlab_pages_enabled = Gitlab.config.pages.enabled
- %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
- = gitlab_pages
- %span.light.pull-right
- = boolean_to_icon gitlab_pages_enabled
- - gitlab_shared_runners = 'Shared Runners'
- - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
- %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
- = gitlab_shared_runners
- %span.light.pull-right
- = boolean_to_icon gitlab_shared_runners_enabled
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Features
+ - sign_up = "Sign up"
+ %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
+ = sign_up
+ %span.light.pull-right
+ = boolean_to_icon signup_enabled?
+ - ldap = "LDAP"
+ %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
+ = ldap
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.ldap.enabled
+ - gravatar = "Gravatar"
+ %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
+ = gravatar
+ %span.light.pull-right
+ = boolean_to_icon gravatar_enabled?
+ - omniauth = "OmniAuth"
+ %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
+ = omniauth
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.omniauth.enabled
+ - reply_email = "Reply by email"
+ %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
+ = reply_email
+ %span.light.pull-right
+ = boolean_to_icon Gitlab::IncomingEmail.enabled?
+ - container_reg = "Container Registry"
+ %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
+ = container_reg
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.registry.enabled
+ - gitlab_pages = 'GitLab Pages'
+ - gitlab_pages_enabled = Gitlab.config.pages.enabled
+ %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
+ = gitlab_pages
+ %span.light.pull-right
+ = boolean_to_icon gitlab_pages_enabled
+ - gitlab_shared_runners = 'Shared Runners'
+ - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
+ %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
+ = gitlab_shared_runners
+ %span.light.pull-right
+ = boolean_to_icon gitlab_shared_runners_enabled
.col-md-4
- %h4
- Components
- - if current_application_settings.version_check_enabled
- .pull-right
- = version_status_badge
-
- %hr
- %p
- GitLab
- %span.pull-right
- = Gitlab::VERSION
- %p
- GitLab Shell
- %span.pull-right
- = Gitlab::Shell.new.version
- %p
- GitLab Workhorse
- %span.pull-right
- = gitlab_workhorse_version
- %p
- GitLab API
- %span.pull-right
- = API::API::version
- %p
- Git
- %span.pull-right
- = Gitlab::Git.version
- %p
- Ruby
- %span.pull-right
- #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
-
- %p
- Rails
- %span.pull-right
- #{Rails::VERSION::STRING}
-
- %p
- = Gitlab::Database.adapter_name
- %span.pull-right
- = Gitlab::Database.version
- %hr
+ .info-well
+ .well-segment.admin-well
+ %h4
+ Components
+ - if current_application_settings.version_check_enabled
+ .pull-right
+ = version_status_badge
+ %p
+ GitLab
+ %span.pull-right
+ = Gitlab::VERSION
+ %p
+ GitLab Shell
+ %span.pull-right
+ = Gitlab::Shell.new.version
+ %p
+ GitLab Workhorse
+ %span.pull-right
+ = gitlab_workhorse_version
+ %p
+ GitLab API
+ %span.pull-right
+ = API::API::version
+ %p
+ Git
+ %span.pull-right
+ = Gitlab::Git.version
+ %p
+ Ruby
+ %span.pull-right
+ #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+ %p
+ Rails
+ %span.pull-right
+ #{Rails::VERSION::STRING}
+ %p
+ = Gitlab::Database.adapter_name
+ %span.pull-right
+ = Gitlab::Database.version
.row
.col-sm-4
- .light-well.well-centered
- %h4 Projects
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_projects_path do
- %h1= number_with_delimiter(Project.cached_count)
+ %h3.text-center
+ Projects:
+ = number_with_delimiter(Project.cached_count)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
- .light-well.well-centered
- %h4 Users
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_users_path do
- %h1= number_with_delimiter(User.count)
+ %h3.text-center
+ Users:
+ = number_with_delimiter(User.count)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
- .light-well.well-centered
- %h4 Groups
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_groups_path do
- %h1= number_with_delimiter(Group.count)
+ %h3.text-center
+ Groups
+ = number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
-
- .row.prepend-top-10
+ .row
.col-md-4
- %h4 Latest projects
- %hr
- - @projects.each do |project|
- %p
- = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
- %span.light.pull-right
- #{time_ago_with_tooltip(project.created_at)}
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest projects
+ - @projects.each do |project|
+ %p
+ = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
+ %span.light.pull-right
+ #{time_ago_with_tooltip(project.created_at)}
.col-md-4
- %h4 Latest users
- %hr
- - @users.each do |user|
- %p
- = link_to [:admin, user], class: 'str-truncated-60' do
- = user.name
- %span.light.pull-right
- #{time_ago_with_tooltip(user.created_at)}
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest users
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated-60' do
+ = user.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(user.created_at)}
.col-md-4
- %h4 Latest groups
- %hr
- - @groups.each do |group|
- %p
- = link_to [:admin, group], class: 'str-truncated-60' do
- = group.full_name
- %span.light.pull-right
- #{time_ago_with_tooltip(group.created_at)}
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest groups
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated-60' do
+ = group.full_name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 9149b8e7fb9..843c71af466 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -107,8 +107,7 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
-
- = render 'shared/members/requests', membership_source: @group, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
@@ -117,7 +116,7 @@
%span.badge= @group.members.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
- %ul.well-list.group-users-list.content-list
+ %ul.well-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 596f367a00d..c69c2761189 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -4,7 +4,7 @@
- @projects.each_with_index do |project|
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
- = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.stats
%span.badge
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 08a8f627113..7b1b15cfeb8 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -108,7 +108,7 @@
.panel-heading
Transfer project
.panel-body
- = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f|
+ = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group
= f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
@@ -128,7 +128,7 @@
.panel-heading
Repository check
.panel-body
- = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
+ = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f|
.form-group
- if @project.last_repository_check_at.nil?
This repository has never been checked.
@@ -160,12 +160,12 @@
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage access')
- %ul.well-list.content-list
+ %ul.well-list.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
@@ -174,7 +174,7 @@
%span.badge= @project.users.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
- %ul.well-list.project_members.content-list
+ %ul.well-list.project_members.content-list.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index d4d166ab7b6..140688b52d3 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -32,13 +32,16 @@
#{time_ago_in_words(runner.contacted_at)} ago
- else
Never
- %td
- .pull-right
- = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
+ %td.admin-runner-btn-group-cell
+ .pull-right.btn-group
+ = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do
+ = icon('pencil')
&nbsp;
- if runner.active?
- = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
+ = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm btn-default has-tooltip', title: 'Pause', ref: 'tooltip', aria: { label: 'Pause' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
+ = icon('pause')
- else
- = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
- = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
-
+ = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do
+ = icon('play')
+ = link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
+ = icon('remove')
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 801430e525e..df2bf27be9d 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -85,7 +85,7 @@
%tr.build
%td.id
- if project
- = link_to namespace_project_job_path(project.namespace, project, build) do
+ = link_to project_job_path(project, build) do
%strong ##{build.id}
- else
%strong ##{build.id}
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 15eaf1c0e67..4a440f3f6d4 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -33,7 +33,7 @@
- member = project.team.find_member(@user.id)
%li.project_member
.list-item-name
- = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
+ = link_to admin_project_path(project), class: dom_class(project) do
= project.name_with_namespace
- if member
@@ -44,5 +44,5 @@
%span.light.vertical-align-middle= member.human_access
- if member.respond_to? :project
- = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
+ = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
%i.fa.fa-times
diff --git a/app/views/projects/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index 98f618ca3b8..98f618ca3b8 100644
--- a/app/views/projects/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
diff --git a/app/views/projects/variables/_form.html.haml b/app/views/ci/variables/_form.html.haml
index 0a70a301cb4..eebd0955c80 100644
--- a/app/views/projects/variables/_form.html.haml
+++ b/app/views/ci/variables/_form.html.haml
@@ -1,12 +1,12 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @variable] do |f|
+= form_for @variable, as: :variable, url: @variable.form_path do |f|
= form_errors(@variable)
.form-group
= f.label :key, "Key", class: "label-light"
- = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
+ = f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true
.form-group
= f.label :value, "Value", class: "label-light"
- = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE"
+ = f.text_area :value, class: "form-control", placeholder: @variable.placeholder
.form-group
.checkbox
= f.label :protected do
diff --git a/app/views/projects/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 5e6786f6698..007c2344b5a 100644
--- a/app/views/projects/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -1,16 +1,16 @@
.row.prepend-top-default.append-bottom-default
.col-lg-4
- = render "projects/variables/content"
+ = render "ci/variables/content"
.col-lg-8
%h5.prepend-top-0
Add a variable
- = render "projects/variables/form", btn_text: "Add new variable"
+ = render "ci/variables/form", btn_text: "Add new variable"
%hr
%h5.prepend-top-0
- Your variables (#{@project.variables.size})
- - if @project.variables.empty?
+ Your variables (#{@variables.size})
+ - if @variables.empty?
%p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above.
- else
- = render "projects/variables/table"
+ = render "ci/variables/table"
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
diff --git a/app/views/ci/variables/_show.html.haml b/app/views/ci/variables/_show.html.haml
new file mode 100644
index 00000000000..2bfb290629d
--- /dev/null
+++ b/app/views/ci/variables/_show.html.haml
@@ -0,0 +1,9 @@
+- page_title "Variables"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ = render "ci/variables/content"
+ .col-lg-9
+ %h5.prepend-top-0
+ Update variable
+ = render "ci/variables/form", btn_text: "Save variable"
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/ci/variables/_table.html.haml
index 59cd3c4b592..71a0b56c4f4 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/ci/variables/_table.html.haml
@@ -11,18 +11,18 @@
%th Protected
%th
%tbody
- - @project.variables.order_key_asc.each do |variable|
+ - @variables.each do |variable|
- if variable.id?
%tr
%td.variable-key= variable.key
%td.variable-value{ "data-value" => variable.value }******
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
%td.variable-menu
- = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
+ = link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
%span.sr-only
Update
= icon("pencil")
- = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
+ = link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
%span.sr-only
Remove
= icon("trash")
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index f893c3e1675..ad35d05c29a 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- @no_container = true
= content_for :meta_tags do
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f9b45a539a1..1cea8182733 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 664ec618b79..ef1467c4d78 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
new file mode 100644
index 00000000000..0319838bdb4
--- /dev/null
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -0,0 +1,33 @@
+.row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("add_new_user", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Add user
+ %p.blank-state-text
+ Add your team members and others to GitLab.
+ = link_to new_admin_user_path, class: "btn btn-new" do
+ New user
+
+.row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("configure_server", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Configure GitLab
+ %p.blank-state-text
+ Make adjustments to how your GitLab instance is set up.
+ = link_to admin_root_path, class: "btn btn-new" do
+ Configure
+
+- if current_user.can_create_group?
+ .row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("add_new_group", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Create a group
+ %p.blank-state-text
+ Groups are a great way to organise projects and people.
+ = link_to new_group_path, class: "btn btn-new" do
+ New group
diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml
new file mode 100644
index 00000000000..a079f0ac1a4
--- /dev/null
+++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml
@@ -0,0 +1,48 @@
+- public_project_count = ProjectsFinder.new(current_user: current_user).execute.count
+
+- if current_user.can_create_group?
+ .row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("add_new_group", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Create a group for several dependent projects.
+ %p.blank-state-text
+ Groups are the best way to manage projects and members.
+ = link_to new_group_path, class: "btn btn-new" do
+ New group
+
+.row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("add_new_project", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Create a project
+ %p.blank-state-text
+ - if current_user.can_create_project?
+ You don't have access to any projects right now.
+ You can create up to
+ %strong= number_with_delimiter(current_user.projects_limit)
+ = succeed "." do
+ = "project".pluralize(current_user.projects_limit)
+ - else
+ If you are added to a project, it will be displayed here.
+ - if current_user.can_create_project?
+ = link_to new_project_path, class: "btn btn-new" do
+ New project
+
+- if public_project_count > 0
+ .row.blank-state.clearfix
+ .col-md-1.col-md-offset-3.blank-state-icon
+ = custom_icon("globe", size: 50)
+ .col-md-5.blank-state-body
+ %h3.blank-state-title
+ Explore public projects
+ %p.blank-state-text
+ There are
+ = number_with_delimiter(public_project_count)
+ public projects on this server.
+ Public projects are an easy way to allow
+ everyone to have read-only access.
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
+ Browse projects
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index 8843d4e7c84..94af033c1e3 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -1,47 +1,12 @@
-- publicish_project_count = ProjectsFinder.new(current_user: current_user).execute.count
-.blank-state.blank-state-welcome
- %h2.blank-state-welcome-title
- Welcome to GitLab
- %p.blank-state-text
- Code, test, and deploy together
-
-- if current_user.can_create_group?
- .blank-state
- .blank-state-icon
- = custom_icon("group", size: 50)
- %h3.blank-state-title
- You can create a group for several dependent projects.
- %p.blank-state-text
- Groups are the best way to manage projects and members.
- = link_to new_group_path, class: "btn btn-new" do
- New group
-
-.blank-state
- .blank-state-icon
- = custom_icon("project", size: 50)
- %h3.blank-state-title
- You don't have access to any projects right now
- %p.blank-state-text
- - if current_user.can_create_project?
- You can create up to
- %strong= number_with_delimiter(current_user.projects_limit)
- = succeed "." do
- = "project".pluralize(current_user.projects_limit)
- - else
- If you are added to a project, it will be displayed here.
- - if current_user.can_create_project?
- = link_to new_project_path, class: "btn btn-new" do
- New project
-
-- if publicish_project_count > 0
- .blank-state
- .blank-state-icon
- = icon("globe")
- %h3.blank-state-title
- There are
- = number_with_delimiter(publicish_project_count)
- public projects on this server.
- %p.blank-state-text
- Public projects are an easy way to allow everyone to have read-only access.
- = link_to trending_explore_projects_path, class: "btn btn-new" do
- Browse projects
+.row.blank-state-parent-container
+ .section-container
+ .container.section-body.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" }
+ .blank-state.blank-state-welcome
+ %h2.blank-state-welcome-title
+ Welcome to GitLab
+ %p.blank-state-text
+ Code, test, and deploy together
+ - if current_user.admin?
+ = render "blank_state_admin_welcome"
+ - else
+ = render "blank_state_welcome"
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 5e63a61e21b..7ac6cf06fb9 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- @hide_top_links = true
+- @breadcrumb_title = "Projects"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 85cbe0bf0e6..e86b1ab3116 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Snippets"
- header_title "Snippets", dashboard_snippets_path
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index f92f89e73ff..e80d10dc8f1 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -6,4 +6,7 @@
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
- = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn')
+ = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
+ %fieldset
+ = check_box_tag :remember_me
+ = label_tag :remember_me, 'Remember Me'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index d696577278d..298604dee8c 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -25,7 +25,7 @@
%div
- if Gitlab::Recaptcha.enabled?
= recaptcha_tags
- %div
+ .submit-container
= f.submit "Register", class: "btn-register btn"
.clearfix.submit-container
%p
diff --git a/app/views/discussions/_new_issue_for_all_discussions.html.haml b/app/views/discussions/_new_issue_for_all_discussions.html.haml
index ca9e0e8728a..cab346fb514 100644
--- a/app/views/discussions/_new_issue_for_all_discussions.html.haml
+++ b/app/views/discussions/_new_issue_for_all_discussions.html.haml
@@ -3,4 +3,4 @@
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
"aria-label" => "Resolve all discussions in a new issue",
"data-container" => "body" }
- = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
+ = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/discussions/_new_issue_for_discussion.html.haml b/app/views/discussions/_new_issue_for_discussion.html.haml
index df5546a1e32..a9bc317b8f8 100644
--- a/app/views/discussions/_new_issue_for_discussion.html.haml
+++ b/app/views/discussions/_new_issue_for_discussion.html.haml
@@ -5,4 +5,4 @@
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
"aria-label" => "Resolve this discussion in a new issue",
"data-container" => "body" }
- = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
+ = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml
index fb6aa30acee..49f90298a50 100644
--- a/app/views/doorkeeper/applications/edit.html.haml
+++ b/app/views/doorkeeper/applications/edit.html.haml
@@ -1,3 +1,4 @@
- page_title "Edit", @application.name, "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
%h3.page-title Edit application
= render 'form', application: @application
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index aa271150b07..d1237d7bf6f 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,7 +1,8 @@
- page_title "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
@@ -10,7 +11,7 @@
and applications that you've authorized to use your account.
- else
Manage applications that you've authorized to use your account.
- .col-lg-9
+ .col-lg-8
- if user_oauth_applications?
%h5.prepend-top-0
Add new application
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 559de63d96d..72eab964766 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -1,4 +1,6 @@
- page_title @application.name, "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
+
%h3.page-title
Application: #{@application.name}
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 3c64f1be5ff..ad434a64556 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
+ = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
&middot;
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index f8f0bcb7608..9fcacfbbf36 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -2,7 +2,7 @@
- event.commits.first(15).each do |commit|
%p
%strong= commit[:author][:name]
- = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id])
+ = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
%i
at
= commit[:timestamp].to_time.to_s(:short)
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 769ac655d0a..54b414cc62a 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -6,7 +6,7 @@
%span.author_name= link_to_author event
%span.pushed #{event.action_name} #{event.ref_type}
%strong
- - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
+ - commits_link = project_commits_path(project, event.ref_name)
= link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
@@ -31,7 +31,7 @@
- from = project.default_branch
- from_label = from
- = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do
+ = link_to project_compare_path(project, from: from, to: event.commit_to) do
Compare #{from_label}...#{truncate_sha(event.commit_to)}
- if create_mr
diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml
index 2454e7355a7..623d233a46a 100644
--- a/app/views/groups/_settings_head.html.haml
+++ b/app/views/groups/_settings_head.html.haml
@@ -12,3 +12,8 @@
= link_to projects_group_path(@group), title: 'Projects' do
%span
Projects
+
+ = nav_link(controller: :ci_cd) do
+ = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
+ %span
+ Pipelines
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 7d5add3cc1c..9ebb3894c55 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -45,10 +45,13 @@
.panel.panel-danger
.panel-heading Remove group
.panel-body
- %p
- Removing group will cause all child projects and resources to be removed.
- %br
- %strong Removed group can not be restored!
+ = form_tag(@group, method: :delete) do
+ %p
+ Removing group will cause all child projects and resources to be removed.
+ %br
+ %strong Removed group can not be restored!
- .form-actions
- = link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+ .form-actions
+ = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
+
+= render 'shared/confirm_modal', phrase: @group.path
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2e4e4511bb6..ad9d5562ded 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -27,6 +27,6 @@
Members with access to
%strong= @group.name
%span.badge= @members.total_count
- %ul.content-list
+ %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
new file mode 100644
index 00000000000..7f450cd9a93
--- /dev/null
+++ b/app/views/groups/milestones/_form.html.haml
@@ -0,0 +1,27 @@
+= form_for [@group, @milestone], html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
+ .row
+ = form_errors(@milestone)
+
+ .col-md-6
+ .form-group
+ = f.label :title, "Title", class: "control-label"
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
+ .form-group.milestone-description
+ = f.label :description, "Description", class: "control-label"
+ .col-sm-10
+ = render layout: 'projects/md_preview', locals: { url: '' } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
+ .clearfix
+ .error-alert
+
+ = render "shared/milestones/form_dates", f: f
+
+ .form-actions
+ - if @milestone.new_record?
+ = f.submit 'Create milestone', class: "btn-create btn"
+ = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
+ - else
+ = f.submit 'Update milestone', class: "btn-create btn"
+ = link_to "Cancel", group_milestone_path(@group, @milestone), class: "btn btn-cancel"
+
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index 4c4e0a26728..bae8997e24c 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,5 +1,6 @@
+
= render 'shared/milestones/milestone',
- milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
+ milestone_path: group_milestone_route(milestone),
issues_path: issues_group_path(@group, milestone_title: milestone.title),
merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
milestone: milestone
diff --git a/app/views/groups/milestones/edit.html.haml b/app/views/groups/milestones/edit.html.haml
new file mode 100644
index 00000000000..5f6d7d209d0
--- /dev/null
+++ b/app/views/groups/milestones/edit.html.haml
@@ -0,0 +1,7 @@
+- page_title "Milestones"
+- render "header_title"
+
+%h3.page-title
+ Edit Milestone
+
+= render "form"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index f91bee0b610..6ceb4092307 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -9,11 +9,6 @@
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
New milestone
-.row-content-block
- Only milestones from
- %strong= @group.name
- group are listed here.
-
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 7c7573862d0..e24844661ee 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -4,40 +4,4 @@
%h3.page-title
New Milestone
-%p.light
- This will create milestone in every selected project
-%hr
-
-= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
- .row
- - if @milestone.errors.any?
- #error_explanation
- .alert.alert-danger
- %ul
- - @milestone.errors.full_messages.each do |msg|
- %li
- = msg
-
- .col-md-6
- .form-group
- = f.label :title, "Title", class: "control-label"
- .col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
- .form-group.milestone-description
- = f.label :description, "Description", class: "control-label"
- .col-sm-10
- = render layout: 'projects/md_preview', locals: { url: '' } do
- = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
- .clearfix
- .error-alert
- .form-group
- = f.label :projects, "Projects", class: "control-label"
- .col-sm-10
- = f.collection_select :project_ids, @group.projects.non_archived, :id, :name,
- { selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2'
-
- = render "shared/milestones/form_dates", f: f
-
- .form-actions
- = f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
+= render "form"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 33e68bc766e..54b1b7a734a 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,4 @@
= render "header_title"
= render 'shared/milestones/top', milestone: @milestone, group: @group
-= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true if @milestone.is_legacy_group_milestone?
= render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 102
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 62ad47972b9..7a2e688a114 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -20,8 +20,8 @@
%span.label.label-warning archived
%span.badge
= storage_counter(project.statistics.storage_size)
- = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
- = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank?
.nothing-here-block This group has no projects yet
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
new file mode 100644
index 00000000000..bf36baf48ab
--- /dev/null
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -0,0 +1,4 @@
+- page_title "Pipelines"
+= render "groups/settings_head"
+
+= render 'ci/variables/index'
diff --git a/app/views/groups/variables/show.html.haml b/app/views/groups/variables/show.html.haml
new file mode 100644
index 00000000000..df533952b76
--- /dev/null
+++ b/app/views/groups/variables/show.html.haml
@@ -0,0 +1 @@
+= render 'ci/variables/show'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 331d1181220..56e628a2b74 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -27,10 +27,11 @@
%td.shortcut
.key f
%td Focus Filter
- %tr
- %td.shortcut
- .key p b
- %td Show/hide the Performance Bar
+ - if performance_bar_enabled?
+ %tr
+ %td.shortcut
+ .key p b
+ %td Show/hide the Performance Bar
%tr
%td.shortcut
.key ?
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index 57e8c3ca1e1..fde671e25a9 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -4,7 +4,7 @@
job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target")
target_field.empty()
- target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
+ target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}')
$("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 882fdf1317d..ad6213b4efd 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -12,7 +12,7 @@
- project = @member.source
project
%strong
- = link_to project.name_with_namespace, namespace_project_url(project.namespace, project)
+ = link_to project.name_with_namespace, project_url(project)
- when Group
- group = @member.source
group
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
index 2ed78bb3b65..0c113c08526 100644
--- a/app/views/issues/_issue.atom.builder
+++ b/app/views/issues/_issue.atom.builder
@@ -1,6 +1,6 @@
xml.entry do
- xml.id namespace_project_issue_url(issue.project.namespace, issue.project, issue)
- xml.link href: namespace_project_issue_url(issue.project.namespace, issue.project, issue)
+ xml.id project_issue_url(issue.project, issue)
+ xml.link href: project_issue_url(issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index f7a1d7e8844..6ad22958df3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -11,6 +11,8 @@
%meta{ property: 'og:title', content: page_title }
%meta{ property: 'og:description', content: page_description }
%meta{ property: 'og:image', content: page_image }
+ %meta{ property: 'og:image:width', content: '64' }
+ %meta{ property: 'og:image:height', content: '64' }
%meta{ property: 'og:url', content: request.base_url + request.fullpath }
-# Twitter Card - https://dev.twitter.com/cards/types/summary
@@ -28,20 +30,21 @@
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
- = stylesheet_link_tag 'peek' if peek_enabled?
+ = stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- if show_new_nav?
= stylesheet_link_tag "new_nav", media: "all"
+ = stylesheet_link_tag "new_sidebar", media: "all"
= Gon::Base.render_data
- = webpack_bundle_tag "runtime"
+ = webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "locale"
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
- = webpack_bundle_tag 'peek' if peek_enabled?
+ = webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 6caaba240bb..4bb0dfc73fd 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -5,10 +5,10 @@
:javascript
gl.GfmAutoComplete = gl.GfmAutoComplete || {};
gl.GfmAutoComplete.dataSources = {
- members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
- issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
+ members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}",
+ issues: "#{issues_project_autocomplete_sources_path(project)}",
+ mergeRequests: "#{merge_requests_project_autocomplete_sources_path(project)}",
+ labels: "#{labels_project_autocomplete_sources_path(project)}",
+ milestones: "#{milestones_project_autocomplete_sources_path(project)}",
+ commands: "#{commands_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}"
};
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index b7df11681d3..cc9219cb6fe 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,13 +1,21 @@
-.page-with-sidebar{ class: page_gutter_class }
- - if defined?(nav) && nav
- .layout-nav
- .container-fluid
- = render "layouts/nav/#{nav}"
- - if content_for?(:sub_nav)
- = yield :sub_nav
- .content-wrapper{ class: layout_nav_class }
+.page-with-sidebar{ class: "#{('page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar)} #{page_gutter_class}" }
+ - if show_new_nav?
+ - if defined?(nav) && nav
+ = render "layouts/nav/#{nav}"
+ - else
+ - if defined?(nav) && nav
+ .layout-nav
+ .container-fluid
+ = render "layouts/nav/#{nav}"
+ - if content_for?(:sub_nav)
+ = yield :sub_nav
+ .content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" }
.alert-wrapper
= render "layouts/broadcast"
+ - if show_new_nav?
+ - if content_for?(:new_global_flash)
+ = yield :new_global_flash
+ = render "layouts/nav/breadcrumbs"
= render "layouts/flash"
= yield :flash_message
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index b689991bb6d..59f16b47bf7 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -5,7 +5,7 @@
- if @group && @group.persisted? && @group.path
- group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
- if @project && @project.persisted?
- - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) }
+ - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) }
.search.search-form{ class: "#{'has-location-badge' if label.present?}" }
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 87064cc9b3f..ae9eee215e0 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,5 +1,9 @@
- page_title "Admin Area"
- header_title "Admin Area", admin_root_path
-- nav "admin"
+- if show_new_nav?
+ - nav "new_admin_sidebar"
+ - @new_sidebar = true
+- else
+ - nav "admin"
= render template: "layouts/application"
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index d879df8fc82..81f83b74826 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -3,7 +3,6 @@
= render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= render "layouts/init_auto_complete" if @gfm_form
- = render 'peek/bar'
- if show_new_nav?
= render "layouts/header/new"
- else
@@ -11,3 +10,5 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
+
+ = render 'peek/bar'
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index f06acc98ca1..35abfa0e80c 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,6 +1,10 @@
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
-- nav "group"
+- if show_new_nav?
+ - nav "new_group_sidebar"
+ - @new_sidebar = true
+- else
+ - nav "group"
= render template: "layouts/application"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index f056c0af968..ed44263741e 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -17,7 +17,7 @@
= link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo
- .title-container
+ .title-container.js-title-container
%h1.title{ class: ('initializing' if @has_group_title) }= title
.navbar-collapse.collapse
@@ -74,9 +74,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- - if can_toggle_new_nav?
- %li
- = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
+ %li
+ = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
@@ -96,4 +95,4 @@
- if @project && !@project.empty_repo?
- if ref = @ref || @project.repository.root_ref
:javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
+ var findFileURL = "#{project_find_file_path(@project, ref)}";
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
index c0833c64911..bee7291da45 100644
--- a/app/views/layouts/header/_new.html.haml
+++ b/app/views/layouts/header/_new.html.haml
@@ -83,11 +83,9 @@
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
- = yield :header_content
-
= render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
- if ref = @ref || @project.repository.root_ref
:javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
+ var findFileURL = "#{project_find_file_path(@project, ref)}";
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 9ff1164f2ee..9da739b0974 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -30,13 +30,13 @@
%li.dropdown-bold-header This project
- if create_project_issue
%li
- = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project)
+ = link_to 'New issue', new_project_issue_path(@project)
- if merge_project
%li
- = link_to 'New merge request', new_namespace_project_merge_request_path(merge_project.namespace, merge_project)
+ = link_to 'New merge request', project_new_merge_request_path(merge_project)
- if create_project_snippet
%li.header-new-project-snippet
- = link_to 'New snippet', new_namespace_project_snippet_path(@project.namespace, @project)
+ = link_to 'New snippet', new_project_snippet_path(@project)
%li.divider
%li.dropdown-bold-header GitLab
- if current_user.can_create_project?
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
new file mode 100644
index 00000000000..5f1641f4300
--- /dev/null
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -0,0 +1,19 @@
+- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize
+- hide_top_links = @hide_top_links || false
+
+%nav.breadcrumbs{ role: "navigation" }
+ .breadcrumbs-container{ class: container_class }
+ .breadcrumbs-links.js-title-container
+ - unless hide_top_links
+ .title
+ = link_to "GitLab", root_path
+ \/
+ = header_title
+ %h2.breadcrumbs-sub-title
+ %ul.list-unstyled
+ - if content_for?(:sub_title_before)
+ = yield :sub_title_before
+ %li= link_to breadcrumb_title, request.path
+ - if content_for?(:breadcrumbs_extra)
+ .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
+ = yield :header_content
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
new file mode 100644
index 00000000000..d7a9e530983
--- /dev/null
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -0,0 +1,123 @@
+.nav-sidebar
+ = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
+ .avatar-container.s40.settings-avatar
+ = icon('wrench')
+ .project-title Admin Area
+ %ul.sidebar-top-level-items
+ = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
+ %span
+ Overview
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview' do
+ %span
+ Overview
+ = nav_link(controller: [:admin, :projects]) do
+ = link_to admin_projects_path, title: 'Projects' do
+ %span
+ Projects
+ = nav_link(controller: :users) do
+ = link_to admin_users_path, title: 'Users' do
+ %span
+ Users
+ = nav_link(controller: :groups) do
+ = link_to admin_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link path: 'jobs#index' do
+ = link_to admin_jobs_path, title: 'Jobs' do
+ %span
+ Jobs
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path, title: 'Runners' do
+ %span
+ Runners
+ = nav_link path: 'cohorts#index' do
+ = link_to admin_cohorts_path, title: 'Cohorts' do
+ %span
+ Cohorts
+
+ = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
+ = link_to admin_conversational_development_index_path, title: 'Monitoring' do
+ %span
+ Monitoring
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :conversational_development_index) do
+ = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
+ %span
+ ConvDev Index
+ = nav_link(controller: :system_info) do
+ = link_to admin_system_info_path, title: 'System Info' do
+ %span
+ System Info
+ = nav_link(controller: :background_jobs) do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %span
+ Background Jobs
+ = nav_link(controller: :logs) do
+ = link_to admin_logs_path, title: 'Logs' do
+ %span
+ Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ %span
+ Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
+
+ = nav_link(controller: :broadcast_messages) do
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
+ %span
+ Messages
+ = nav_link(controller: [:hooks, :hook_logs]) do
+ = link_to admin_hooks_path, title: 'Hooks' do
+ %span
+ System Hooks
+
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ %span
+ Applications
+
+ = nav_link(controller: :abuse_reports) do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
+ %span
+ Abuse Reports
+ %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+
+ - if akismet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ %span
+ Spam Logs
+
+ = nav_link(controller: :deploy_keys) do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+ %span
+ Deploy Keys
+
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ %span
+ Service Templates
+
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels' do
+ %span
+ Labels
+
+ = nav_link(controller: :appearances) do
+ = link_to admin_appearances_path, title: 'Appearances' do
+ %span
+ Appearance
+
+ %li.divider
+ = nav_link(controller: :application_settings) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
new file mode 100644
index 00000000000..7b68d11041e
--- /dev/null
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -0,0 +1,61 @@
+.nav-sidebar
+ = link_to group_path(@group), title: @group.name, class: 'context-header' do
+ .avatar-container.s40.group-avatar
+ = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
+ .group-title
+ = @group.name
+ %ul.sidebar-top-level-items
+ = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Home' do
+ %span
+ Group
+
+ %ul.sidebar-sub-level-items
+ = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Group Home' do
+ %span
+ Home
+
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ %span
+ Activity
+
+ = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
+ = link_to issues_group_path(@group), title: 'Issues' do
+ %span
+ Issues
+ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ %span.badge.count= number_with_delimiter(issues.count)
+
+ %ul.sidebar-sub-level-items
+ = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
+ = link_to issues_group_path(@group), title: 'List' do
+ %span
+ List
+
+ = nav_link(path: 'labels#index') do
+ = link_to group_labels_path(@group), title: 'Labels' do
+ %span
+ Labels
+
+ = nav_link(path: 'milestones#index') do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ %span
+ Milestones
+
+ = nav_link(path: 'groups#merge_requests') do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ %span
+ Merge Requests
+ - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+ %span.badge.count= number_with_delimiter(merge_requests.count)
+ = nav_link(path: 'group_members#index') do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ %span
+ Members
+ - if current_user && can?(current_user, :admin_group, @group)
+ = nav_link(path: %w[groups#projects groups#edit]) do
+ = link_to edit_group_path(@group), title: 'Settings' do
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
new file mode 100644
index 00000000000..033ea149cfb
--- /dev/null
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -0,0 +1,53 @@
+.nav-sidebar
+ = link_to profile_path, title: 'Profile Settings', class: 'context-header' do
+ .avatar-container.s40.settings-avatar
+ = icon('user')
+ .project-title User Settings
+ %ul.sidebar-top-level-items
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: 'Profile Settings' do
+ %span
+ Profile
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
+ = link_to profile_account_path, title: 'Account' do
+ %span
+ Account
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ %span
+ Applications
+ = nav_link(controller: :chat_names) do
+ = link_to profile_chat_names_path, title: 'Chat' do
+ %span
+ Chat
+ = nav_link(controller: :personal_access_tokens) do
+ = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+ %span
+ Access Tokens
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path, title: 'Emails' do
+ %span
+ Emails
+ - unless current_user.ldap_user?
+ = nav_link(controller: :passwords) do
+ = link_to edit_profile_password_path, title: 'Password' do
+ %span
+ Password
+ = nav_link(controller: :notifications) do
+ = link_to profile_notifications_path, title: 'Notifications' do
+ %span
+ Notifications
+
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ %span
+ SSH Keys
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences' do
+ %span
+ Preferences
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Authentication log' do
+ %span
+ Authentication log
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
new file mode 100644
index 00000000000..cc731db3cc1
--- /dev/null
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -0,0 +1,247 @@
+.nav-sidebar
+ - can_edit = can?(current_user, :admin_project, @project)
+ = link_to project_path(@project), title: @project.name, class: 'context-header' do
+ .avatar-container.s40.project-avatar
+ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
+ .project-title
+ = @project.name
+ %ul.sidebar-top-level-items
+ = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+ %span
+ Project
+
+ %ul.sidebar-sub-level-items
+ = nav_link(path: 'projects#show') do
+ = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do
+ %span= _('Home')
+
+ = nav_link(path: 'projects#activity') do
+ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
+ %span= _('Activity')
+
+ - if can?(current_user, :read_cycle_analytics, @project)
+ = nav_link(path: 'cycle_analytics#show') do
+ = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
+ %span= _('Cycle Analytics')
+
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
+ = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ %span
+ Repository
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_tree_path(@project) do
+ #{ _('Files') }
+
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to project_commits_path(@project, current_ref) do
+ #{ _('Commits') }
+
+ = nav_link(html_options: {class: branches_tab_class}) do
+ = link_to project_branches_path(@project) do
+ #{ _('Branches') }
+
+ = nav_link(controller: [:tags, :releases]) do
+ = link_to project_tags_path(@project) do
+ #{ _('Tags') }
+
+ = nav_link(path: 'graphs#show') do
+ = link_to project_graph_path(@project, current_ref) do
+ #{ _('Contributors') }
+
+ = nav_link(controller: %w(network)) do
+ = link_to project_network_path(@project, current_ref) do
+ #{ s_('ProjectNetworkGraph|Graph') }
+
+ = nav_link(controller: :compare) do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
+ #{ _('Compare') }
+
+ = nav_link(path: 'graphs#charts') do
+ = link_to charts_project_graph_path(@project, current_ref) do
+ #{ _('Charts') }
+
+ - if project_nav_tab? :container_registry
+ = nav_link(controller: %w[projects/registry/repositories]) do
+ = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ %span
+ Registry
+
+ - if project_nav_tab? :issues
+ = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
+ %span
+ Issues
+ - if @project.default_issues_tracker?
+ %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ %ul.sidebar-sub-level-items
+ - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+ = nav_link(controller: :issues) do
+ = link_to project_issues_path(@project), title: 'Issues' do
+ %span
+ List
+
+ = nav_link(controller: :boards) do
+ = link_to project_boards_path(@project), title: 'Board' do
+ %span
+ Board
+
+ - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+ = nav_link(controller: :merge_requests) do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
+ %span
+ Merge Requests
+
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to project_labels_path(@project), title: 'Labels' do
+ %span
+ Labels
+
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
+ %span
+ Milestones
+
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ %span
+ Merge Requests
+ %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
+
+ %ul.sidebar-sub-level-items
+ - if project_nav_tab? :pipelines
+ = nav_link(path: ['pipelines#index', 'pipelines#show']) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
+
+ - if project_nav_tab? :builds
+ = nav_link(controller: [:jobs, :artifacts]) do
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ %span
+ Jobs
+
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipeline_schedules) do
+ = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+ %span
+ Schedules
+
+ - if project_nav_tab? :environments
+ = nav_link(controller: :environments) do
+ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ %span
+ Environments
+
+ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
+ = nav_link(path: 'pipelines#charts') do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ %span
+ Charts
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+ %span
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
+ %span
+ Snippets
+
+ - if project_nav_tab? :settings
+ = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
+ = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+
+ %ul.sidebar-sub-level-items
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if can_edit
+ = nav_link(controller: :projects) do
+ = link_to edit_project_path(@project), title: 'General' do
+ %span
+ General
+ = nav_link(controller: :members) do
+ = link_to project_settings_members_path(@project), title: 'Members' do
+ %span
+ Members
+ - if can_edit
+ = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
+ = link_to project_settings_integrations_path(@project), title: 'Integrations' do
+ %span
+ Integrations
+ = nav_link(controller: :repository) do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
+ %span
+ Repository
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(controller: :ci_cd) do
+ = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
+ %span
+ Pipelines
+ - if Gitlab.config.pages.enabled
+ = nav_link(controller: :pages) do
+ = link_to project_pages_path(@project), title: 'Pages' do
+ %span
+ Pages
+
+ - else
+ = nav_link(path: %w[members#show]) do
+ = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+ %span
+ Settings
+
+ -# Shortcut to Project > Activity
+ %li.hidden
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+ %span
+ Activity
+
+ -# Shortcut to Repository > Graph (formerly, Network)
+ - if project_nav_tab? :network
+ %li.hidden
+ = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ Graph
+
+ -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
+ - unless @project.empty_repo?
+ %li.hidden
+ = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ Charts
+
+ -# Shortcut to Issues > New Issue
+ %li.hidden
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
+ Create a new issue
+
+ -# Shortcut to Pipelines > Jobs
+ - if project_nav_tab? :builds
+ %li.hidden
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ Jobs
+
+ -# Shortcut to commits page
+ - if project_nav_tab? :commits
+ %li.hidden
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+ Commits
+
+ -# Shortcut to issue boards
+ %li.hidden
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 29658da7792..fb90bb4b472 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -12,30 +12,32 @@
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
- = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span
Registry
- if project_nav_tab? :issues
= nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
- if @project.default_issues_tracker?
- %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
- if project_nav_tab? :merge_requests
- = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ - controllers = [:merge_requests, 'projects/merge_requests/conflicts']
+ - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker?
+ = nav_link(controller: controllers) do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
- %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(issuables_count_for_state(:merge_requests, :opened, finder: MergeRequestsFinder.new(current_user, project_id: @project.id)))
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :environments, :artifacts]) do
@@ -51,20 +53,21 @@
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
%span
Snippets
+ - if project_nav_tab? :project_members
+ = nav_link(controller: :project_members) do
+ = link_to project_project_members_path(@project), title: 'Members', class: 'shortcuts-members' do
+ %span
+ Members
+
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
- - else
- = nav_link(path: %w[members#show]) do
- = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
- %span
- Settings
-# Shortcut to Project > Activity
%li.hidden
@@ -75,18 +78,18 @@
-# Shortcut to Repository > Graph (formerly, Network)
- if project_nav_tab? :network
%li.hidden
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
Graph
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- unless @project.empty_repo?
%li.hidden
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
Charts
-# Shortcut to Issues > New Issue
%li.hidden
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
Create a new issue
-# Shortcut to Pipelines > Jobs
@@ -103,4 +106,4 @@
-# Shortcut to issue boards
%li.hidden
- = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 0ee8a57dbd4..c365839e605 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,6 +1,10 @@
- page_title "User Settings"
- header_title "User Settings", profile_path unless header_title
- sidebar "dashboard"
-- nav "profile"
+- if show_new_nav?
+ - nav "new_profile_sidebar"
+ - @new_sidebar = true
+- else
+ - nav "profile"
= render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 3f5b0c54e50..99adb83cd1f 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,13 +1,17 @@
- page_title @project.name_with_namespace
- page_description @project.description unless page_description
- header_title project_title(@project) unless header_title
-- nav "project"
+- if show_new_nav?
+ - nav "new_project_sidebar"
+ - @new_sidebar = true
+- else
+ - nav "project"
- content_for :project_javascripts do
- project = @target_project || @project
- if current_user
:javascript
- window.uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
+ window.uploads_path = "#{project_uploads_path(project)}";
- content_for :header_content do
.js-dropdown-menu-projects
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index bc12e38675f..b35d4b7502d 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
Issue was closed by #{@updated_by.name}
-Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
+Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index d0c96b83976..c4e06cb3cb1 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
index 40f7d61fe19..472c31e9a5e 100644
--- a/app/views/notify/issue_moved_email.html.haml
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -2,5 +2,5 @@
Issue was moved to another project.
%p
New issue:
- = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
+ = link_to project_issue_url(@new_project, @new_issue) do
= @new_issue.title
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
index b3bd43c2055..66ede43635b 100644
--- a/app/views/notify/issue_moved_email.text.erb
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -1,4 +1,4 @@
Issue was moved to another project.
New issue location:
-<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
+<%= project_issue_url(@new_project, @new_issue) %>
diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
index e6ab3fcde77..4200881f7e8 100644
--- a/app/views/notify/issue_status_changed_email.text.erb
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -1,4 +1,4 @@
Issue was <%= @issue_status %> by <%= @updated_by.name %>
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 4c9719ba732..ae2a2933865 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 46c1c9dee0b..661c23bcbe2 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was merged
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 13f1ac08e94..3c716f77296 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,6 +1,6 @@
New Issue was created.
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb
index f19ac3adfc7..23213106c5b 100644
--- a/app/views/notify/new_mention_in_issue_email.text.erb
+++ b/app/views/notify/new_mention_in_issue_email.text.erb
@@ -1,6 +1,6 @@
You have been mentioned in an issue.
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb
index 5bf0282e097..6fcebb22fc4 100644
--- a/app/views/notify/new_mention_in_merge_request_email.text.erb
+++ b/app/views/notify/new_mention_in_merge_request_email.text.erb
@@ -1,6 +1,6 @@
You have been mentioned in Merge Request <%= @merge_request.to_reference %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 3c8f178ac77..7d98400e6fe 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New Merge Request <%= @merge_request.to_reference %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index 3def26342a1..f0ba7827cef 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -2,7 +2,7 @@
Project #{@project.name} was exported successfully.
%p
The project export can be downloaded from:
- = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do
+ = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do
= @project.name_with_namespace + " export"
%p
The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_exported_email.text.erb b/app/views/notify/project_was_exported_email.text.erb
index 42c4d176876..cd3a1f7934f 100644
--- a/app/views/notify/project_was_exported_email.text.erb
+++ b/app/views/notify/project_was_exported_email.text.erb
@@ -1,6 +1,6 @@
Project <%= @project.name %> was exported successfully.
The project export can be downloaded from:
-<%= download_export_namespace_project_url(@project.namespace, @project) %>
+<%= download_export_project_url(@project) %>
The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 87b3ff7f0b3..c476a39b661 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -2,7 +2,7 @@
Project #{@old_path_with_namespace} was moved to another location
%p
The project is now located under
- = link_to namespace_project_url(@project.namespace, @project) do
+ = link_to project_url(@project) do
= @project.name_with_namespace
%p
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index b2c5f71e465..7c45163e0e8 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,7 +1,7 @@
Project <%= @old_path_with_namespace %> was moved to another location
The project is now located under
-<%= namespace_project_url(@project.namespace, @project) %>
+<%= project_url(@project) %>
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 546376aeed8..5c5520f4cb8 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -3,7 +3,7 @@
%h3
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
- at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
+ at #{link_to(@message.project_name_with_namespace, project_url(@message.project))}
- if @message.compare
- if @message.reverse_compare?
@@ -17,7 +17,7 @@
%ul
- @message.commits.each do |commit|
%li
- %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))
+ %strong= link_to(commit.short_id, project_commit_url(@message.project, commit))
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)}
diff --git a/app/views/notify/resolved_all_discussions_email.text.erb b/app/views/notify/resolved_all_discussions_email.text.erb
index b0d380af8fc..2881f3e699e 100644
--- a/app/views/notify/resolved_all_discussions_email.text.erb
+++ b/app/views/notify/resolved_all_discussions_email.text.erb
@@ -1,3 +1,3 @@
All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= @resolved_by.name %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/peek/views/_rblineprof.html.haml b/app/views/peek/views/_rblineprof.html.haml
new file mode 100644
index 00000000000..6c037930ca9
--- /dev/null
+++ b/app/views/peek/views/_rblineprof.html.haml
@@ -0,0 +1,7 @@
+Profile:
+
+= link_to 'all', url_for(lineprofiler: 'true'), class: 'js-toggle-modal-peek-line-profile'
+\/
+= link_to 'app & lib', url_for(lineprofiler: 'app'), class: 'js-toggle-modal-peek-line-profile'
+\/
+= link_to 'views', url_for(lineprofiler: 'views'), class: 'js-toggle-modal-peek-line-profile'
diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml
index 16fc010f66f..dd8b524064f 100644
--- a/app/views/peek/views/_sql.html.haml
+++ b/app/views/peek/views/_sql.html.haml
@@ -1,13 +1,13 @@
%strong
- %a#peek-show-queries{ href: '#' }
+ %a.js-toggle-modal-peek-sql
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-pg-queries.modal{ tabindex: -1 }
- .modal-dialog
- #modal-peek-pg-queries-content.modal-content
+ .modal-dialog.modal-full
+ .modal-content
.modal-header
- %a.close{ href: "#", "data-dismiss" => "modal" } ×
+ %button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X
%h4
SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a319b18e507..ed079ed7dfb 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Account"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
- if current_user.ldap_user?
@@ -6,13 +7,13 @@
Some options are unavailable for LDAP accounts
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .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-9.private-tokens-reset
+ .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.' }
@@ -22,12 +23,12 @@
%hr
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .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).
- .col-lg-9
+ .col-lg-8
%p
Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
- if current_user.two_factor_enabled?
@@ -43,12 +44,12 @@
%hr
- if button_based_providers.any?
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Social sign-in
%p
Activate signin with one of the following services
- .col-lg-9
+ .col-lg-8
%label.label-light
Connected Accounts
%p Click on icon to activate signin with one of the following services
@@ -69,12 +70,12 @@
%hr
- if current_user.can_change_username?
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.warning-title
Change username
%p
Changing your username will change path to all personal projects!
- .col-lg-9
+ .col-lg-8
= form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group
= f.label :username, "Path", class: "label-light"
@@ -93,10 +94,10 @@
- if signup_enabled?
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.danger-title
Remove account
- .col-lg-9
+ .col-lg-8
- if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p
Deleting an account has the following effects:
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index a24b7fd101d..1a392e29e2a 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,11 +1,12 @@
- page_title "Authentication log"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h3.prepend-top-0
= page_title
%p
This is a security log of important events involving your account.
- .col-lg-9
+ .col-lg-8
= render 'event_table', events: @events
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 1ec1e7c70e4..fe1cf802971 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -10,7 +10,7 @@
%td
%strong
- if can?(current_user, :admin_project, project)
- = link_to service.title, edit_namespace_project_service_path(project.namespace, project, service)
+ = link_to service.title, edit_project_service_path(project, service)
- else
= service.title
%td
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 20cc636b2da..8f7121afe02 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -1,14 +1,15 @@
- page_title 'Chat'
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
You can see your Chat accounts.
- .col-lg-9
+ .col-lg-8
%h5 Active chat names (#{@chat_names.size})
- if @chat_names.present?
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index f5a323dbaf8..612ecbbb96a 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,13 +1,14 @@
- page_title "Emails"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
Control emails linked to your account
- .col-lg-9
+ .col-lg-8
%h4.prepend-top-0
Add email address
= form_for 'email', url: profile_emails_path do |f|
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 71b224a413b..5f7b41cf30e 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,13 +1,14 @@
- page_title "SSH Keys"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
SSH keys allow you to establish a secure connection between your computer and GitLab.
- .col-lg-9
+ .col-lg-8
%h5.prepend-top-0
Add an SSH key
%p.profile-settings-content
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 6283ceebf10..172c0450381 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,3 +1,4 @@
- page_title @key.title, "SSH Keys"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 51c4e8e5a73..e98fdfc7a3d 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Notifications"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
%div
@@ -10,14 +11,14 @@
= hidden_field_tag :notification_type, 'global'
.row
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4
= page_title
%p
You can specify notification level per group or per project.
%p
By default, all projects and groups will use the global notifications setting.
- .col-lg-9
+ .col-lg-8
%h5
Global notification settings
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 243428b690e..985bb79508f 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,12 +1,13 @@
- page_title "Password"
+- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
After a successful password update, you will be redirected to the login page where you can log in with your new password.
- .col-lg-9
+ .col-lg-8
%h5.prepend-top-0
Change your password
- unless @user.password_automatically_set?
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index c852107e69a..cf750378e25 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,8 +1,9 @@
- page_title "Personal Access Tokens"
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
@@ -11,7 +12,7 @@
You can also use personal access tokens to authenticate against Git over HTTP.
They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
- .col-lg-9
+ .col-lg-8
- if flash[:personal_access_token]
.created-personal-access-token-container
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 0b5995415e9..bd602071384 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,49 +1,47 @@
- page_title 'Preferences'
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Syntax highlighting theme
%p
This setting allows you to customize the appearance of the syntax.
= succeed '.' do
= link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
- .col-lg-9.syntax-theme
+ .col-lg-8.syntax-theme
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
- - if can_toggle_new_nav?
- .col-sm-12
- %hr
- .col-lg-3.profile-settings-sidebar#new-navigation
- %h4.prepend-top-0
- New Navigation
- %p
- This setting allows you to turn on or off the new upcoming navigation concept.
- = succeed '.' do
- = link_to 'Learn more', '', target: '_blank'
- .col-lg-9.syntax-theme
- = label_tag do
- .preview= image_tag "old_nav.png"
- %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
- Old
- = label_tag do
- .preview= image_tag "new_nav.png"
- %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
- New
.col-sm-12
%hr
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar#new-navigation
+ %h4.prepend-top-0
+ New Navigation
+ %p
+ This setting allows you to turn on or off the new upcoming navigation concept.
+ .col-lg-8.syntax-theme
+ = label_tag do
+ .preview= image_tag "old_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
+ Old
+ = label_tag do
+ .preview= image_tag "new_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
+ New
+ .col-sm-12
+ %hr
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Behavior
%p
This setting allows you to customize the behavior of the system layout and default views.
= succeed '.' do
= link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
- .col-lg-9
+ .col-lg-8
.form-group
= f.label :layout, class: 'label-light' do
Layout width
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 819c98946ab..bac75a49075 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,10 +1,11 @@
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default' }, authenticity_token: true do |f|
= form_errors(@user)
.row
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Public Avatar
%p
@@ -16,7 +17,7 @@
You can upload an avatar here
- if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
- .col-lg-9
+ .col-lg-8
.clearfix.avatar-image.append-bottom-default
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
@@ -34,14 +35,14 @@
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray'
%hr
.row
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Main settings
%p
This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
- .col-lg-9
+ .col-lg-8
.row
= f.text_field :name, required: true, wrapper: { class: 'col-md-9' },
help: 'Enter your name, so people you know can recognize you.'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 0ff05098cd7..67792de3870 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,5 +1,6 @@
- page_title 'Two-Factor Authentication', 'Account'
- header_title "Two-Factor Authentication", profile_two_factor_auth_path
+- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
- if inject_u2f_api?
@@ -7,12 +8,12 @@
= page_specific_javascript_bundle_tag('u2f')
.row.prepend-top-default
- .col-lg-3
+ .col-lg-4
%h4.prepend-top-0
Register Two-Factor Authentication App
%p
Use an app on your mobile device to enable two-factor authentication (2FA).
- .col-lg-9
+ .col-lg-8
- if current_user.two_factor_otp_enabled?
= icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page."
- else
@@ -20,9 +21,9 @@
Download the Google Authenticator application from App Store or Google Play Store and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
.row.append-bottom-10
- .col-md-3
+ .col-md-4
= raw @qr_code
- .col-md-9
+ .col-md-8
.account-well
%p.prepend-top-0.append-bottom-0
Can't scan the code?
@@ -50,7 +51,7 @@
.row.prepend-top-default
- .col-lg-3
+ .col-lg-4
%h4.prepend-top-0
Register Universal Two-Factor (U2F) Device
%p
@@ -59,7 +60,7 @@
As U2F devices are only supported by a few browsers, we require that you set up a
two-factor authentication app before a U2F device. That way you'll always be able to
log in - even when you're using an unsupported browser.
- .col-lg-9
+ .col-lg-8
- if @u2f_registration.errors.present?
= form_errors(@u2f_registration)
= render "u2f/register"
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 10f581d751b..ecc966ed453 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,7 +1,7 @@
%div{ class: container_class }
.nav-block.activity-filter-block.activities
.controls
- = link_to namespace_project_path(@project.namespace, @project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
+ = link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml
index cb4d2bbacf5..da1b2d7f9b6 100644
--- a/app/views/projects/_find_file_link.html.haml
+++ b/app/views/projects/_find_file_link.html.haml
@@ -1,3 +1,3 @@
-= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
+= link_to project_find_file_path(@project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
= icon('search')
%span= _('Find file')
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f1ef50d2de2..1a71bfca2e2 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -5,7 +5,7 @@
.event-last-push-text
%span You pushed to
%strong
- = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name'
+ = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project
%span at
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 2bab22e125d..a56c3503c77 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -14,5 +14,5 @@
Add a homepage to your wiki that contains information about your project
%p
We recommend you
- = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
+ = link_to "add a homepage", project_wiki_path(@project, :home)
to your project's wiki and GitLab will show it here instead of this message.
diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml
index e2966ec33c2..03be6f15313 100644
--- a/app/views/projects/artifacts/_tree_directory.html.haml
+++ b/app/views/projects/artifacts/_tree_directory.html.haml
@@ -1,4 +1,4 @@
-- path_to_directory = browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: directory.path)
+- path_to_directory = browse_project_job_artifacts_path(@project, @build, path: directory.path)
%tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index ea0b43b85cf..8edb9be049a 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -1,4 +1,4 @@
-- path_to_file = file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: file.path)
+- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
%tr.tree-item{ 'data-link' => path_to_file }
- blob = file.blob
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 961c805dc7c..576e5b385af 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -6,17 +6,17 @@
.tree-holder
.nav-block
.tree-controls
- = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build),
+ = link_to download_project_job_artifacts_path(@project, @build),
rel: 'nofollow', download: '', class: 'btn btn-default download' do
= icon('download')
Download artifacts archive
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
- path_breadcrumbs do |title, path|
%li
- = link_to truncate(title, length: 40), browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path)
.tree-content-holder
%table.table.tree-table
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index b25c7c95196..18e86ac5a92 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -7,15 +7,15 @@
.nav-block
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
- path_breadcrumbs do |title, path|
- title = truncate(title, length: 40)
%li
- if path == @path
- = link_to file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) do
+ = link_to file_project_job_artifacts_path(@project, @build, path) do
%strong= title
- else
- = link_to title, browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to title, browse_project_job_artifacts_path(@project, @build, path)
%article.file-holder
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 3627f72f5e1..f11afe8fc22 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -22,9 +22,9 @@
= author_avatar(commit, size: 36)
.commit-row-title
%strong
- = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
+ = link_to_gfm truncate(commit.title, length: 35), project_commit_path(@project, commit.id), class: "cdark"
.pull-right
- = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
&nbsp;
.light
= commit_author_link(commit, avatar: false)
diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml
index 2c944b516a4..1c148de9678 100644
--- a/app/views/projects/blob/_breadcrumb.html.haml
+++ b/app/views/projects/blob/_breadcrumb.html.haml
@@ -6,16 +6,16 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
- path_breadcrumbs do |title, path|
- title = truncate(title, length: 40)
%li
- if path == @path
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, path)) do
+ = link_to project_blob_path(@project, tree_join(@ref, path)) do
%strong= title
- else
- = link_to title, namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
+ = link_to title, project_tree_path(@project, tree_join(@ref, path))
.tree-controls
= render 'projects/find_file_link'
@@ -24,14 +24,14 @@
-# only show normal/blame view links for text files
- if blob.readable_text?
- if blame
- = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id),
+ = link_to 'Normal view', project_blob_path(@project, @id),
class: 'btn'
- else
- = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
+ = link_to 'Blame', project_blame_path(@project, @id),
class: 'btn js-blob-blame-link' unless blob.empty?
- = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
+ = link_to 'History', project_commits_path(@project, @id),
class: 'btn'
- = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ = link_to 'Permalink', project_blob_path(@project,
tree_join(@commit.sha, @path)), class: 'btn js-data-file-blob-permalink-url'
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 40978583e8b..b2959ef6d31 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,7 +5,7 @@
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title= _('Create New Directory')
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
+ = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
.form-group
= label_tag :dir_name, _('Directory name'), class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index c8ca0406213..6a4a657fa8c 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -6,7 +6,7 @@
%h3.page-title Delete #{@blob.name}
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
+ = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
= render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
.form-group
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 4af62461151..f8cb612a2b4 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -9,7 +9,7 @@
- if @conflict
.alert.alert-danger
Someone edited the file the same time you did. Please check out
- = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
+ = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs.
.editor-title-row
%h3.page-title.blob-edit-page-title
@@ -22,13 +22,13 @@
Write
%li
- = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
= editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+ = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
- = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
+ = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id)
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 2afb909572a..8620a470041 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -7,10 +7,10 @@
New file
= render 'template_selectors'
.file-editor
- = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
+ = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
- cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
+ cancel_path: project_tree_path(@project, @id)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 41f75a491a5..6e2ae4717cd 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -16,4 +16,4 @@
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/blob/viewers/_changelog.html.haml b/app/views/projects/blob/viewers/_changelog.html.haml
index 53921e63b5f..46e3e7f798a 100644
--- a/app/views/projects/blob/viewers/_changelog.html.haml
+++ b/app/views/projects/blob/viewers/_changelog.html.haml
@@ -1,4 +1,4 @@
= icon('history fw')
= succeed '.' do
To find the state of this project's repository at the time of any of these versions, check out
- = link_to "the tags", namespace_project_tags_path(viewer.project.namespace, viewer.project)
+ = link_to "the tags", project_tags_path(viewer.project)
diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
index 334b33faf48..d8492abc638 100644
--- a/app/views/projects/blob/viewers/_readme.html.haml
+++ b/app/views/projects/blob/viewers/_readme.html.haml
@@ -1,4 +1,4 @@
= icon('info-circle fw')
= succeed '.' do
To learn more about this project, read
- = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project)
+ = link_to "the wiki", get_project_wiki_path(viewer.project)
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 6684ecfce81..07272ea2df1 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -2,6 +2,10 @@
- @content_class = "issue-boards-content"
- page_title "Boards"
+- if show_new_nav?
+ - content_for :sub_title_before do
+ %li= link_to "Issues", project_issues_path(@project)
+
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
@@ -30,7 +34,7 @@
":key" => "_uid" }
= render "projects/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
- "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
+ "new-issue-path" => new_project_issue_path(@project),
"milestone-path" => milestones_filter_dropdown_path,
"label-path" => labels_filter_path,
":issue-link-base" => "issueLinkBase",
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml
index 24d76da6f06..09d70f658a3 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/projects/boards/components/_sidebar.html.haml
@@ -23,4 +23,5 @@
= render "projects/boards/components/sidebar/labels"
= render "projects/boards/components/sidebar/notifications"
%remove-btn{ ":issue" => "issue",
- ":list" => "list" }
+ ":list" => "list",
+ "v-if" => "canRemove" }
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml
index e8db868f49b..8d957613be1 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml
@@ -19,10 +19,11 @@
":data-name" => "assignee.name",
":data-username" => "assignee.username" }
.dropdown
- %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
+ - dropdown_options = issue_assignees_dropdown_options
+ %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
- Select assignee
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+ = dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to")
diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml
index 1a3b88e28c5..f44a9d49a54 100644
--- a/app/views/projects/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml
@@ -23,7 +23,7 @@
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml
index bee0f3dd065..7d0c35fe183 100644
--- a/app/views/projects/boards/components/sidebar/_labels.html.haml
+++ b/app/views/projects/boards/components/sidebar/_labels.html.haml
@@ -19,8 +19,8 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
- data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text
Label
= icon('chevron-down')
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml
index 4e46351bf8a..002e9994ee0 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml
@@ -16,10 +16,10 @@
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown
- %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
+ %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml
index a08c7f2af09..aaddd7e249f 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml
@@ -1,5 +1,5 @@
- if current_user
- .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
+ .block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" }
%span.issuable-header-text.hide-collapsed.pull-left
Notifications
%button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 869633e016d..19712a8f1be 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -6,7 +6,7 @@
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
%li{ class: "js-branch-#{branch.name}" }
%div
- = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated ref-name' do
+ = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork')
= branch.name
&nbsp;
@@ -25,7 +25,7 @@
Merge request
- if branch.name != @repository.root_ref
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
Compare
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
@@ -42,7 +42,7 @@
title: "Delete protected branch",
data: { toggle: "modal",
target: "#modal-delete-branch",
- delete_path: namespace_project_branch_path(@project.namespace, @project, branch.name),
+ delete_path: project_branch_path(@project, branch.name),
branch_name: branch.name } }
= icon("trash-o")
- else
@@ -51,7 +51,7 @@
title: "Only a project master or owner can delete a protected branch" }
= icon("trash-o")
- else
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
+ = link_to project_branch_path(@project, branch.name),
class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: "Delete branch",
method: :delete,
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index ad8f9da0621..18fbb81c167 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,9 +1,9 @@
.branch-commit
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
&middot;
%span.str-truncated
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 4bade77a077..8bc1996452b 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -6,7 +6,7 @@
.top-area.adjust
.nav-text
Protected branches can be managed in
- = link_to 'project settings', namespace_project_protected_branches_path(@project.namespace, @project)
+ = link_to 'project settings', project_protected_branches_path(@project)
.nav-controls
= form_tag(filter_branches_path, method: :get) do
@@ -25,9 +25,9 @@
= link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
- if can? current_user, :push_code, @project
- = link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
+ = link_to project_merged_branches_path(@project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
Delete merged branches
- = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ = link_to new_project_branch_path(@project), class: 'btn btn-create' do
New branch
- if @branches.any?
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 5a0eba3551f..03eefcc2b4d 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -27,7 +27,7 @@
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index a73ddd5eb33..883922dbf04 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -10,19 +10,19 @@
%li.dropdown-header
#{ _('Source code') }
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download zip')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.gz')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.bz2')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar')
@@ -37,7 +37,7 @@
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
- = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
+ = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span
#{ s_('DownloadArtifacts|Download') } '#{job.name}'
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 960b57a8008..b04d6a1fa5e 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -10,19 +10,19 @@
- if can_create_issue
%li
- = link_to new_namespace_project_issue_path(@project.namespace, @project) do
+ = link_to new_project_issue_path(@project) do
= icon('exclamation-circle fw')
#{ _('New issue') }
- if merge_project
%li
- = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
+ = link_to project_new_merge_request_path(merge_project) do
= icon('tasks fw')
#{ _('New merge request') }
- if can_create_snippet
%li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
+ = link_to new_project_snippet_path(@project) do
= icon('file-text-o fw')
#{ _('New snippet') }
@@ -31,28 +31,28 @@
- if can?(current_user, :push_code, @project)
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+ = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
%li
- = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') }
%li
- = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project)
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+ = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
- elsif can?(current_user, :fork_project, @project)
%li
- - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
+ - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 42f8c75f57b..f45cc7f0f45 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -5,14 +5,14 @@
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- elsif !current_user.can_create_project?
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
+ = link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do
+ = link_to new_project_fork_path(@project), class: 'btn' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
%span.arrow
- = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
+ = link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
= @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 58413e2fc52..e248676be0d 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,5 +1,5 @@
- if current_user
- = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
+ = link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project)
= icon('star')
%span.starred= _('Unstar')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index d9f28d66b66..c1842527480 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to namespace_project_job_url(job.project.namespace, job.project, job) do
+ = link_to project_job_url(job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
@@ -30,7 +30,7 @@
= custom_icon("icon_commit")
- if commit_sha
- = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-sha"
+ = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha"
- if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
@@ -63,7 +63,7 @@
- if admin
%td
- if job.project
- = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project)
+ = link_to job.project.name_with_namespace, admin_project_path(job.project)
%td
- if job.try(:runner)
= runner_link(job.runner)
@@ -95,16 +95,16 @@
%td
.pull-right
- if can?(current_user, :read_build, job) && job.artifacts?
- = link_to download_namespace_project_job_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
+ = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
- = link_to cancel_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
+ = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- if job.playable? && !admin && can?(current_user, :update_build, job)
- = link_to play_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play')
- elsif job.retryable?
- = link_to retry_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 2267f123e38..d0a380516f9 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -22,7 +22,7 @@
= label_tag 'start_branch', branch_label, class: 'control-label'
.col-sm-10
= hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch'
- = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
+ = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: project_branches_path(@project), submit_form_on_click: false } })
- if can?(current_user, :push_code, @project)
= render 'shared/new_merge_request_checkbox'
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 8aed88da38b..f3f11b5b405 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,10 +1,10 @@
%ul.nav-links.no-top.no-bottom.commit-ci-menu
= nav_link(path: 'commit#show') do
- = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ = link_to project_commit_path(@project, @commit.id) do
Changes
%span.badge= @diffs.size
- if can?(current_user, :read_pipeline, @project)
= nav_link(path: 'commit#pipelines') do
- = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ = link_to pipelines_project_commit_path(@project, @commit.id) do
Pipelines
%span.badge= @commit.pipelines.size
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 7fe44816bae..572c368990e 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -21,7 +21,7 @@
%span.btn.disabled.btn-grouped.hidden-xs.append-right-10
= icon('comment')
= @notes_count
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
+ = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
#{ _('Browse files') }
.dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
@@ -29,22 +29,22 @@
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
+ = link_to project_tree_path(@project, @commit) do
_('Browse Files')
- unless @commit.has_been_reverted?(current_user)
%li.clearfix
- = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
%li.clearfix
- = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project?
%li.clearfix
- = link_to s_("CreateTag|Tag"), new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
+ = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider
%li.dropdown-header
#{ _('Download') }
- unless @commit.parents.length > 1
- %li= link_to s_("DownloadCommit|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
- %li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
+ %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
+ %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
.commit-box
%h3.commit-title
@@ -59,7 +59,7 @@
= custom_icon("icon_commit")
%span.cgray= n_('parent', 'parents', @commit.parents.count)
- @commit.parents.each do |parent|
- = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
+ = link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
%span.commit-info.branches
%i.fa.fa-spinner.fa-spin
@@ -67,10 +67,10 @@
- last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info
.status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
- = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
+ = link_to project_pipeline_path(@project, last_pipeline.id) do
= ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') }
- = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
+ = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
@@ -80,4 +80,4 @@
= time_interval_in_words last_pipeline.duration
:javascript
- $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
+ $(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}");
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index ac93eac41ac..c66ea873dba 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -2,4 +2,4 @@
= render 'commit_box'
= render 'ci_menu'
-= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)
+= render 'projects/commit/pipelines_list', endpoint: pipelines_project_commit_path(@project, @commit.id)
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
index 1657fb46163..d806acdda13 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -1,6 +1,6 @@
xml.entry do
- xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id)
- xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
+ xml.id project_commit_url(@project, id: commit.id)
+ xml.link href: project_commit_url(@project, id: commit.id)
xml.title truncate(commit.title, length: 80)
xml.updated commit.committed_date.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 8a4ef5a45b3..1033bad0d49 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -16,7 +16,7 @@
.commit-detail
.commit-content
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message item-title"
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
@@ -39,6 +39,6 @@
.commit-actions.flex-row.hidden-xs
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha btn btn-transparent"
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 93fd0789c11..c764e35dd2a 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -8,8 +8,8 @@
%li.commits-row{ data: { day: day } }
%ul.content-list.commit-list
- = render commits, project: project, ref: ref
+ = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
- if hidden > 0
%li.alert.alert-warning
- = n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
+ = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index ebeaab863bc..e1549baef89 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -4,33 +4,33 @@
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project) do
+ = link_to project_tree_path(@project) do
#{ _('Files') }
= nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ = link_to project_commits_path(@project, current_ref) do
#{ _('Commits') }
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = link_to project_branches_path(@project) do
#{ _('Branches') }
= nav_link(controller: [:tags, :releases]) do
- = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = link_to project_tags_path(@project) do
#{ _('Tags') }
= nav_link(path: 'graphs#show') do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to project_graph_path(@project, current_ref) do
#{ _('Contributors') }
= nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ = link_to project_network_path(@project, current_ref) do
#{ s_('ProjectNetworkGraph|Graph') }
= nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
#{ _('Compare') }
= nav_link(path: 'graphs#charts') do
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to charts_project_graph_path(@project, current_ref) do
#{ _('Charts') }
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index 5fb89935467..48cefbe45f2 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,8 +1,8 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha"
&nbsp;
%span.str-truncated
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
.pull-right
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 9cf792e1721..a9b77631474 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name}:#{@ref} commits"
-xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html"
-xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
+xml.link href: project_commits_url(@project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html"
+xml.id project_commits_url(@project, @ref)
xml.updated @commits.first.committed_date.xmlschema if @commits.any?
xml << render(@commits) if @commits.any?
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index fabd825aec8..b8547c10c73 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -2,33 +2,34 @@
- page_title _("Commits"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= content_for :sub_nav do
= render "head"
%div{ class: container_class }
- .row-content-block.second-block.content-component-block.flex-container-block
- .tree-ref-holder
- = render 'shared/ref_switcher', destination: 'commits'
+ .tree-holder
+ .nav-block
+ .tree-ref-container
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'commits'
+
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
+ .tree-controls.hidden-xs.hidden-sm
+ - if @merge_request.present?
+ .control
+ = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
+ - elsif create_mr_button?(@repository.root_ref, @ref)
+ .control
+ = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
-
- .block-controls.hidden-xs.hidden-sm
- - if @merge_request.present?
.control
- = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- - elsif create_mr_button?(@repository.root_ref, @ref)
+ = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form') do
+ = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control
- = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
-
- .control
- = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
- = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
- .control
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
- = icon("rss")
+ = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
+ = icon("rss")
%div{ id: dom_id(@project) }
%ol#commits-list.list-unstyled.content_list
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index adb724c1b8d..94b7db5eb25 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,4 +1,4 @@
-= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
+= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
.compare-switch-container
@@ -7,7 +7,7 @@
.input-group.inline-input-group
%span.input-group-addon from
= hidden_field_tag :from, params[:from]
- = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
@@ -15,12 +15,12 @@
.input-group.inline-input-group
%span.input-group-addon to
= hidden_field_tag :to, params[:to]
- = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render 'shared/ref_dropdown'
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?
- = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
+ = link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
= link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn'
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index cb98ce04430..45985a5ecef 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -12,4 +12,4 @@
Create a new deploy key for this project
= render @deploy_keys.form_partial_path
%hr
- #js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } }
+ #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project) } }
diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml
index 37219f8d7ae..cd910b82b57 100644
--- a/app/views/projects/deploy_keys/edit.html.haml
+++ b/app/views/projects/deploy_keys/edit.html.haml
@@ -7,4 +7,4 @@
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
= f.submit 'Save changes', class: 'btn-save btn'
- = link_to 'Cancel', namespace_project_settings_repository_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_settings_repository_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 4502c397d29..4c22166c256 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -6,12 +6,12 @@
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-sha"
+ = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha"
%p.commit-title.flex-truncate-parent
%span.flex-truncate-child
- if commit_title = deployment.commit_title
= author_avatar(deployment.commit, size: 20)
- = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
+ = link_to_gfm commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index d538c4c86c8..f9385459a66 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -10,7 +10,7 @@
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
- - elsif current_controller?(:merge_requests)
+ - elsif current_controller?('projects/merge_requests/diffs')
= diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
- elsif current_controller?(:compare)
= diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 43708d22a0c..cd0fb21f8a7 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -19,6 +19,7 @@
- if plain
= link_text
- else
+ = add_diff_note_button(line_code, diff_file.position(line), type)
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- discussion = line_discussions.try(:first)
- if discussion && discussion.resolvable? && !plain
@@ -29,7 +30,7 @@
= link_text
- else
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }<
+ %td.line_content.noteable_line{ class: type }<
- if email
%pre= line.text
- else
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 8e5f4d2573d..56d63250714 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,4 +1,5 @@
/ Side-by-side diff view
+
.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
%table
- diff_file.parallel_diff_lines.each do |line|
@@ -18,11 +19,12 @@
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
%td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
+ = add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id }
- %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
+ %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
@@ -38,11 +40,12 @@
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
%td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
+ = add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id }
- %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
+ %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 295a1b62535..da34a83d8e0 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -2,13 +2,12 @@
%h4
Too many changes to show.
.pull-right
- - if current_controller?(:commit) or current_controller?(:merge_requests)
- - if current_controller?(:commit)
- = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
- = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
- - elsif @merge_request && @merge_request.persisted?
- = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
- = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
+ - if current_controller?(:commit)
+ = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-sm"
+ = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-sm"
+ - elsif current_controller?('projects/merge_requests/diffs') && @merge_request&.persisted?
+ = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
+ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p
To preserve performance only
%strong #{diff_files.size} of #{diff_files.real_size}
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
index 19d08181223..33d3dcbeafa 100644
--- a/app/views/projects/diffs/viewers/_image.html.haml
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -15,7 +15,7 @@
.two-up.view
%span.wrap
.frame.deleted
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
+ %a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
%img{ src: old_blob_raw_path, alt: diff_file.old_path }
%p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size)
@@ -27,7 +27,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) }
+ %a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
%img{ src: blob_raw_path, alt: diff_file.new_path }
%p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 78057facde7..087cb804449 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -134,7 +134,7 @@
.help-block The maximum file size allowed is 200KB.
- if @project.avatar?
%hr
- = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+ = 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"
.row.prepend-top-default
@@ -148,7 +148,7 @@
Runs a number of housekeeping tasks within the current repository,
such as compressing file revisions and removing unreachable objects.
.col-lg-8
- = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
+ = link_to 'Housekeeping', housekeeping_project_path(@project),
method: :post, class: "btn btn-default"
%hr
.row.prepend-top-default
@@ -164,12 +164,12 @@
.col-lg-8
- if @project.export_project_path
- = link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Download export', download_export_project_path(@project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Generate new export', generate_new_export_project_path(@project),
method: :post, class: "btn btn-default"
- else
- = link_to 'Export project', export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Export project', export_project_path(@project),
method: :post, class: "btn btn-default"
.bs-callout.bs-callout-info
@@ -207,13 +207,13 @@
- if @project.archived?
%p
%strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+ = link_to 'Unarchive project', unarchive_project_path(@project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
method: :post, class: "btn btn-success"
- else
%p
%strong Archived projects cannot be committed to!
- = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+ = link_to 'Archive project', archive_project_path(@project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
method: :post, class: "btn btn-warning"
%hr
@@ -252,7 +252,7 @@
%p.append-bottom-0
Please select the group you want to transfer this project to in the dropdown to the right.
.col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group
= label_tag :new_namespace_id, nil, class: 'label-light' do
%span Select a new namespace
@@ -276,7 +276,7 @@
= succeed "." do
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
.col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
+ = 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.
= button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
@@ -289,7 +289,7 @@
%p.append-bottom-0
Removing the project will delete its repository and all related resources including issues, merge requests etc.
.col-lg-8
- = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do
+ = form_tag(project_path(@project), method: :delete) do
%p
%strong Removed projects cannot be restored!
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 50e0bad3ccf..0f132a68ce1 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,6 +1,7 @@
- @no_container = true
+- flash_message_container = show_new_nav? ? :new_global_flash : :flash_message
-= content_for :flash_message do
+= content_for flash_message_container do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index 6d040f5cfe6..1605f3a3351 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -19,4 +19,4 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml
index 14a2d627203..c35f9af2873 100644
--- a/app/views/projects/environments/_stop.html.haml
+++ b/app/views/projects/environments/_stop.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :create_deployment, environment) && environment.stop_action?
.inline
- = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post,
+ = link_to stop_project_environment_path(@project, environment), method: :post,
class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do
= icon('stop', class: 'stop-env-icon')
diff --git a/app/views/projects/environments/_terminal_button.html.haml b/app/views/projects/environments/_terminal_button.html.haml
index 97de9c95de7..a6201bdbc42 100644
--- a/app/views/projects/environments/_terminal_button.html.haml
+++ b/app/views/projects/environments/_terminal_button.html.haml
@@ -1,3 +1,3 @@
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
- = link_to terminal_namespace_project_environment_path(@project.namespace, @project, environment), class: 'btn terminal-button' do
+ = link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal')
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 80d2b6f5d95..30cdbc5ae04 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -12,6 +12,6 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"project-environments-path" => project_environments_path(@project),
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
- "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
+ "new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments"),
"css-class" => container_class } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index e8f8fbbcf09..e9e1ad9ef30 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -1,80 +1,21 @@
- @no_container = true
- page_title "Metrics for environment", @environment.name
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('common_d3')
- = page_specific_javascript_bundle_tag('monitoring')
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'common_d3'
+ = webpack_bundle_tag 'monitoring'
= render "projects/pipelines/head"
-#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
+.prometheus-container{ class: container_class }
.top-area
.row
.col-sm-6
- %h3.page-title
+ %h3
Environment:
= link_to @environment.name, environment_path(@environment)
- .prometheus-state
- .js-getting-started.hidden
- .row
- .col-md-4.col-md-offset-4.state-svg
- = render "shared/empty_states/monitoring/getting_started.svg"
- .row
- .col-md-6.col-md-offset-3
- %h4.text-center.state-title
- Get started with performance monitoring
- .row
- .col-md-6.col-md-offset-3
- .description-text.text-center.state-description
- Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.
- = link_to help_page_path('administration/monitoring/prometheus/index.md') do
- Learn more about performance monitoring
- .row.state-button-section
- .col-md-4.col-md-offset-4.text-center.state-button
- = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), class: 'btn btn-success' do
- Configure Prometheus
- .js-loading.hidden
- .row
- .col-md-4.col-md-offset-4.state-svg
- = render "shared/empty_states/monitoring/loading.svg"
- .row
- .col-md-6.col-md-offset-3
- %h4.text-center.state-title
- Waiting for performance data
- .row
- .col-md-6.col-md-offset-3
- .description-text.text-center.state-description
- Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
- .row.state-button-section
- .col-md-4.col-md-offset-4.text-center.state-button
- = link_to help_page_path('administration/monitoring/prometheus/index.md'), class: 'btn btn-success' do
- View documentation
- .js-unable-to-connect.hidden
- .row
- .col-md-4.col-md-offset-4.state-svg
- = render "shared/empty_states/monitoring/unable_to_connect.svg"
- .row
- .col-md-6.col-md-offset-3
- %h4.text-center.state-title
- Unable to connect to Prometheus server
- .row
- .col-md-6.col-md-offset-3
- .description-text.text-center.state-description
- Ensure connectivity is available from the GitLab server to the
- = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus') do
- Prometheus server
- .row.state-button-section
- .col-md-4.col-md-offset-4.text-center.state-button
- = link_to help_page_path('administration/monitoring/prometheus/index.md'), class:'btn btn-success' do
- View documentation
+ #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
+ "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
+ "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+ "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
- .prometheus-graphs
- .row
- .col-sm-12
- %h4
- CPU utilization
- %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
- .row
- .col-sm-12
- %h4
- Memory usage
- %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 31e2bb11ce8..0ce0f5465fc 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -12,9 +12,9 @@
= render 'projects/environments/external_url', environment: @environment
= render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment)
- = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
+ = link_to 'Edit', edit_project_environment_path(@project, @environment), class: 'btn'
- if can?(current_user, :stop_environment, @environment)
- = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
+ = link_to 'Stop', stop_project_environment_path(@project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.environments-container
- if @deployments.blank?
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 4c4aa0baff3..464135b5ac7 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -22,4 +22,4 @@
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }
- #terminal{ data: { project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws" } }
+ #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } }
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 8a409541fe5..e3bf48ee47f 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -7,7 +7,7 @@
= render 'shared/ref_switcher', destination: 'find_file', path: @path
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
%li.file-finder
%input#file_find.form-control.file-finder-input{ type: "text", placeholder: _('Find by path'), autocomplete: 'off' }
@@ -20,8 +20,8 @@
:javascript
var projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
- url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}",
- treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}",
- blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}"
+ url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}",
+ treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}",
+ blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}"
});
new ShortcutsFindFile(projectFindFile);
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index 524b77783ef..d365bcd4ecc 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -20,6 +20,6 @@
= error
%p
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
+ = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to fork again
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index f4aa523b32d..111cbcda266 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -34,7 +34,7 @@
= custom_icon('icon_fork')
%span Fork
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
+ = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
= custom_icon('icon_fork')
%span Fork
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 5242bc72b71..0f36e1a7353 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -30,7 +30,7 @@
= namespace.human_name
- else
.fork-thumbnail
- = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
+ = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index b23bbadbdb4..b98dc09534f 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -20,14 +20,14 @@
- if generic_commit_status.ref
.icon-container
= generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ = link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
- else
.light none
.icon-container.commit-icon
= custom_icon("icon_commit")
- if commit_sha
- = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
+ = link_to generic_commit_status.short_sha, project_commit_path(generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
- if retried
= icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
@@ -53,7 +53,7 @@
- if admin
%td
- if generic_commit_status.project
- = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project)
+ = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project)
%td
- if generic_commit_status.try(:runner)
= runner_link(generic_commit_status.runner)
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 680f8ae6c8f..640e0d689ca 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -35,7 +35,7 @@
:javascript
$.ajax({
type: "GET",
- url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}",
+ url: "#{project_graph_path(@project, current_ref, format: :json)}",
dataType: "json",
success: function (data) {
var graph = new ContributorsStatGraph();
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
index 6962b223451..05b06cfc8b2 100644
--- a/app/views/projects/hook_logs/_index.html.haml
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -28,7 +28,7 @@
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
- = link_to 'View details', namespace_project_hook_hook_log_path(project.namespace, project, hook, hook_log)
+ = link_to 'View details', project_hook_hook_log_path(project, hook, hook_log)
= paginate hook_logs, theme: 'gitlab'
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index 2eabe92f8eb..ab5a7b117d7 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -6,6 +6,6 @@
Request details
.col-lg-9
- = link_to 'Resend Request', retry_namespace_project_hook_hook_log_path(@project.namespace, @project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
+ = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index fd382c1d63f..4944e0c8041 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -9,14 +9,13 @@
#{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
used for binding events when something is happening within the project.
.col-lg-9.append-bottom-default
- = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
- = link_to 'Test hook', test_namespace_project_hook_path(@project.namespace, @project, @hook), class: 'btn btn-default'
- = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
+ = link_to 'Test hook', test_project_hook_path(@project, @hook), class: 'btn btn-default'
+ = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
= render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
-
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 25a87411cac..778ff91362d 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -12,7 +12,7 @@
:preserve
#{h(sanitize_repo_path(@project, @project.import_error))}
-= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
+= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
= render "shared/import_form", f: f
.form-actions
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 7a188cb6445..e9f21594a71 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -5,29 +5,29 @@
%ul{ class: (container_class) }
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= nav_link(controller: :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+ = link_to project_issues_path(@project), title: 'Issues' do
%span
List
= nav_link(controller: :boards) do
- = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
+ = link_to project_boards_path(@project), title: 'Board' do
%span
Board
- if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
%span
Merge Requests
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
Milestones
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 9e4e6934ca9..7dc35be57a6 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -4,43 +4,49 @@
.issue-check.hidden
= check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
.issue-info-container
- .issue-title.title
- %span.issue-title-text
- = confidential_icon(issue)
- = link_to issue.title, issue_path(issue)
+ .issue-main-info
+ .issue-title.title
+ %span.issue-title-text
+ = confidential_icon(issue)
+ = link_to issue.title, issue_path(issue)
+ - if issue.tasks?
+ %span.task-status.hidden-xs
+ &nbsp;
+ = issue.task_status
+
+ .issuable-info
+ %span.issuable-reference
+ #{issuable_reference(issue)}
+ %span.issuable-authored.hidden-xs
+ &middot;
+ opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, issue.author, avatar: false)}
+ - if issue.milestone
+ %span.issuable-milestone.hidden-xs
+ &nbsp;
+ = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do
+ = icon('clock-o')
+ = issue.milestone.title
+ - if issue.due_date
+ %span.issuable-due-date.hidden-xs{ class: "#{'cred' if issue.overdue?}" }
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
+ - if issue.labels.any?
+ &nbsp;
+ - issue.labels.each do |label|
+ = link_to_label(label, subject: issue.project, css_class: 'label-link')
+
+ .issuable-meta
%ul.controls
- if issue.closed?
- %li
+ %li.issuable-status
CLOSED
-
- if issue.assignees.any?
%li
= render 'shared/issuable/assignees', project: @project, issue: issue
= render 'shared/issuable_meta_data', issuable: issue
- .issue-info
- #{issuable_reference(issue)} &middot;
- opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
- by #{link_to_member(@project, issue.author, avatar: false)}
- - if issue.milestone
- &nbsp;
- = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
- = icon('clock-o')
- = issue.milestone.title
- - if issue.due_date
- %span{ class: "#{'cred' if issue.overdue?}" }
- &nbsp;
- = icon('calendar')
- = issue.due_date.to_s(:medium)
- - if issue.labels.any?
- &nbsp;
- - issue.labels.each do |label|
- = link_to_label(label, subject: issue.project, css_class: 'label-link')
- - if issue.tasks?
- &nbsp;
- %span.task-status
- = issue.task_status
-
- .pull-right.issue-updated-at
+ .pull-right.issuable-updated-at.hidden-xs
%span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
index 35b7d1b920c..264032a3a31 100644
--- a/app/views/projects/issues/_issue_by_email.html.haml
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -30,5 +30,5 @@
Anyone who gets ahold of it can create issues as if they were you.
You should
- = link_to 'reset it', new_issue_address_namespace_project_path(@project.namespace, @project), class: 'incoming-email-token-reset'
+ = link_to 'reset it', new_issue_address_project_path(@project), class: 'incoming-email-token-reset'
if that ever happens.
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index bda52fe461c..6a567487514 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -18,7 +18,7 @@
- unless @issue.project.id == merge_request.target_project.id
in
- project = merge_request.target_project
- = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+ = link_to project.name_with_namespace, project_path(project)
- if merge_request.merged?
%span.merge-request-status.prepend-left-10.merged
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
new file mode 100644
index 00000000000..756faf4625e
--- /dev/null
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -0,0 +1,10 @@
+= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
+ = icon('rss')
+- if @can_bulk_update
+ = button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
+= link_to "New issue", new_project_issue_path(@project,
+ issue: { assignee_id: issues_finder.assignee.try(:id),
+ milestone_id: issues_finder.milestones.first.try(:id) }),
+ class: "btn btn-new",
+ title: "New issue",
+ id: "new_issue_link"
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index dba092c8844..e1b4a49850a 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :push_code, @project)
- .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue), create_mr_path: create_merge_request_namespace_project_issue_path(@project.namespace, @project, @issue), create_branch_path: namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
+ .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_project_issue_path(@project, @issue), create_mr_path: create_merge_request_project_issue_path(@project, @issue), create_branch_path: project_branches_path(@project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
.btn-group.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 8c9f6f3b4df..1df38db9fd4 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -11,4 +11,4 @@
= render_pipeline_status(pipeline)
%span.related-branch-info
%strong
- = link_to branch, namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "ref-name"
+ = link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 61346884346..4029926f373 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name} issues"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id namespace_project_issues_url(@project.namespace, @project)
+xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html"
+xml.id project_issues_url(@project)
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7183794ce72..aacb057840d 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -13,23 +13,16 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
+- if show_new_nav?
+ - content_for :breadcrumbs_extra do
+ = render "projects/issues/nav_btns"
+
- if project_issues(@project).exists?
%div{ class: (container_class) }
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls
- = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
- = icon('rss')
- - if @can_bulk_update
- = button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle"
- = link_to new_namespace_project_issue_path(@project.namespace,
- @project,
- issue: { assignee_id: issues_finder.assignee.try(:id),
- milestone_id: issues_finder.milestones.first.try(:id) }),
- class: "btn btn-new",
- title: "New issue",
- id: "new_issue_link" do
- New issue
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ = render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
@@ -40,4 +33,4 @@
- if new_issue_email
= render 'issue_by_email', email: new_issue_email
- else
- = render 'shared/empty_states/issues', button_path: new_namespace_project_issue_path(@project.namespace, @project)
+ = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index d909b0bfbbd..a57844f974e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -30,27 +30,26 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_issue
- %li
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'issuable-edit'
- %li
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- %li
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li= link_to 'Edit', edit_project_issue_path(@project, @issue)
+ - unless current_user == @issue.author
+ %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
+ - if can_update_issue
+ %li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam
- %li
- = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
+ %li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can_update_issue || can_report_spam
%li.divider
- %li
- = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
+
+ = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
+
- if can_report_spam
- = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
+ = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details
@@ -65,10 +64,10 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
- #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript.
- #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript.
.content-block.emoji-block
diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index ad72ab5b199..d81b8f6bb4c 100644
--- a/app/views/projects/jobs/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -6,13 +6,13 @@
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
%strong
Job
- = link_to "##{@build.id}", namespace_project_job_path(@project.namespace, @project, @build), class: 'js-build-id'
+ = link_to "##{@build.id}", project_job_path(@project, @build), class: 'js-build-id'
in pipeline
%strong
= link_to "##{pipeline.id}", pipeline_path(pipeline)
for
%strong
- = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: 'commit-sha'
+ = link_to pipeline.short_sha, project_commit_path(@project, pipeline.sha), class: 'commit-sha'
from
%strong
= link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name'
@@ -24,8 +24,8 @@
- if show_controls
.nav-controls
- if can?(current_user, :create_issue, @project) && @build.failed?
- = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+ = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
+ = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 93e8a4e385c..bddb587ddc6 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -26,14 +26,14 @@
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
- = link_to keep_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
+ = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
- = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
+ = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
- if @build.artifacts_metadata?
- = link_to browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+ = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse
- if @build.trigger_request
@@ -58,7 +58,7 @@
.block
%p
Commit
- = link_to @build.pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha), class: 'commit-sha link-commit'
+ = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
= clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
- if @build.merge_request
in
@@ -73,9 +73,9 @@
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
- = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit'
+ = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
from
- = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit'
+ = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.stage-selection More
= icon('chevron-down')
@@ -88,7 +88,7 @@
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- = link_to namespace_project_job_path(@project.namespace, @project, build) do
+ = link_to project_job_path(@project, build) do
= icon('arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index a33e3978ee1..8604c7d3ea4 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -10,7 +10,7 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel running', cancel_all_namespace_project_jobs_path(@project.namespace, @project),
+ = link_to 'Cancel running', cancel_all_project_jobs_path(@project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index c73bae0a2c9..fa086413fbe 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -21,7 +21,7 @@
%br
Go to
- = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
+ = link_to project_runners_path(@build.project) do
Runners page
- if @build.starts_environment?
@@ -54,23 +54,24 @@
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- .build-trace-container#build-trace
- .top-bar.sticky
+ .build-trace-container.prepend-top-default
+ .top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden<
Showing last
%span.js-truncated-info-size.truncated-info-size><
KiB of log -
- %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
+ %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
+
.controllers
- if @build.has_trace?
- = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
+ = link_to raw_project_job_path(@project, @build),
title: 'Show complete raw',
data: { placement: 'top', container: 'body' },
class: 'js-raw-link-controller has-tooltip controllers-buttons' do
= icon('file-text-o')
- if can?(current_user, :update_build, @project) && @build.erasable?
- = link_to erase_namespace_project_job_path(@project.namespace, @project, @build),
+ = link_to erase_project_job_path(@project, @build),
method: :post,
data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
title: 'Erase job log',
@@ -82,15 +83,17 @@
.has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
%button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
= custom_icon('scroll_down')
- .bash.sticky.js-scroll-container
- %code.js-build-output
+
+ %pre.build-trace#build-trace
+ %code.bash.js-build-output
.build-loader-animation.js-build-refresh
+
= render "sidebar"
.js-build-options{ data: javascript_build_options }
-#js-job-details-vue{ data: { endpoint: namespace_project_job_path(@project.namespace, @project, @build, format: :json) } }
+#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 7f0059cdcda..84b0b65d1c0 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -6,4 +6,4 @@
%h3.page-title
Edit Label
%hr
- = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project)
+ = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fc72c4fb635..8fbc4588902 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -11,7 +11,7 @@
.nav-controls
- if can?(current_user, :admin_label, @project)
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+ = link_to new_project_label_path(@project), class: "btn btn-new" do
New label
.labels
@@ -20,7 +20,7 @@
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
- %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
+ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.present?
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 8f6c085a361..79e90b7ca3b 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -6,4 +6,4 @@
%h3.page-title
New Label
%hr
- = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project)
+ = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index aac74a25b75..243dcfdc187 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -13,4 +13,4 @@
and try again.
%hr
.clearfix
- = link_to 'Go back', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg pull-right'
+ = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg pull-right'
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 04bd4e8b683..3bdb5d0adc4 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -2,7 +2,7 @@
This service will be installed on the Mattermost instance at
%strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
%hr
-= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
+= form_for(:mattermost, method: :post, url: project_mattermost_path(@project), html: { class: 'js-requires-input'} ) do |f|
%h4 Team
%p
= @teams.one? ? 'The team' : 'Select the team'
@@ -42,5 +42,5 @@
%hr
.clearfix
.pull-right
- = link_to 'Cancel', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg'
+ = link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg'
= f.submit 'Install', class: 'btn btn-save btn-lg'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/_commits.html.haml
index 11793919ff7..11793919ff7 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/_commits.html.haml
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index b7f73fe5339..1e505222887 100644
--- a/app/views/projects/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -4,18 +4,18 @@
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
%span
List
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
Milestones
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
index 766cb272bec..766cb272bec 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c13110deb16..0a1ebcb8124 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,58 +4,60 @@
= check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
.issue-info-container
- .merge-request-title.title
- %span.merge-request-title-text
- = link_to merge_request.title, merge_request_path(merge_request)
+ .issue-main-info
+ .merge-request-title.title
+ %span.merge-request-title-text
+ = link_to merge_request.title, merge_request_path(merge_request)
+ - if merge_request.tasks?
+ %span.task-status.hidden-xs
+ &nbsp;
+ = merge_request.task_status
+
+ .issuable-info
+ %span.issuable-reference
+ #{issuable_reference(merge_request)}
+ %span.issuable-authored.hidden-xs
+ &middot;
+ opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, merge_request.author, avatar: false)}
+ - if merge_request.milestone
+ %span.issuable-milestone.hidden-xs
+ &nbsp;
+ = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do
+ = icon('clock-o')
+ = merge_request.milestone.title
+ - if merge_request.target_project.default_branch != merge_request.target_branch
+ %span.project-ref-path
+ &nbsp;
+ = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
+ = icon('code-fork')
+ = merge_request.target_branch
+ - if merge_request.labels.any?
+ &nbsp;
+ - merge_request.labels.each do |label|
+ = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+
+ .issuable-meta
%ul.controls
- if merge_request.merged?
- %li
+ %li.issuable-status.hidden-xs
MERGED
- elsif merge_request.closed?
- %li
+ %li.issuable-status.hidden-xs
= icon('ban')
CLOSED
-
- if merge_request.head_pipeline
- %li
+ %li.issuable-pipeline-status.hidden-xs
= render_pipeline_status(merge_request.head_pipeline)
-
- if merge_request.open? && merge_request.broken?
- %li
+ %li.issuable-pipeline-broken.hidden-xs
= link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle')
-
- if merge_request.assignee
%li
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
= render 'shared/issuable_meta_data', issuable: merge_request
- .merge-request-info
- #{issuable_reference(merge_request)} &middot;
- opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
- by #{link_to_member(@project, merge_request.author, avatar: false)}
- - if merge_request.target_project.default_branch != merge_request.target_branch
- &nbsp;
- = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
- = icon('code-fork')
- = merge_request.target_branch
-
- - if merge_request.milestone
- &nbsp;
- = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
- = icon('clock-o')
- = merge_request.milestone.title
-
- - if merge_request.labels.any?
- &nbsp;
- - merge_request.labels.each do |label|
- = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
-
- - if merge_request.tasks?
- &nbsp;
- %span.task-status
- = merge_request.task_status
-
- .pull-right.hidden-xs
+ .pull-right.issuable-updated-at.hidden-xs
%span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
index 8a390cf8700..8a390cf8700 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/_mr_box.html.haml
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
new file mode 100644
index 00000000000..a2e819fb3a7
--- /dev/null
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -0,0 +1,40 @@
+- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
+
+- if @merge_request.closed_without_fork?
+ .alert.alert-danger
+ %p The source project of this merge request has been removed.
+
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
+ = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs
+ = @merge_request.state_human_name
+
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issuable-meta
+ = issuable_meta(@merge_request, @project, "Merge request")
+
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
+ Options
+ = icon('caret-down')
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can_update_merge_request
+ %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+ - unless current_user == @merge_request.author
+ %li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
+ - if can_update_merge_request
+ %li{ class: merge_request_button_visibility(@merge_request, true) }
+ = 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'
+
+ - 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"
+
+ = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
new file mode 100644
index 00000000000..e92f2712347
--- /dev/null
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -0,0 +1,5 @@
+- if @can_bulk_update
+ = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
+- if merge_project
+ = link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
+ New merge request
diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/_pipelines.html.haml
index 2f1dbe87619..473b7b919c8 100644
--- a/app/views/projects/merge_requests/show/_pipelines.html.haml
+++ b/app/views/projects/merge_requests/_pipelines.html.haml
@@ -1,4 +1,4 @@
-- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
+- endpoint_path = local_assigns[:endpoint] || pipelines_project_merge_request_path(@project, @merge_request, format: :json)
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
deleted file mode 100644
index 75120409bb3..00000000000
--- a/app/views/projects/merge_requests/_show.html.haml
+++ /dev/null
@@ -1,97 +0,0 @@
-- @content_class = "limit-container-width" unless fluid_layout
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
-- page_description @merge_request.description
-- page_card_attributes @merge_request.card_attributes
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('common_vue')
- = page_specific_javascript_bundle_tag('diff_notes')
-
-.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
- = render "projects/merge_requests/show/mr_title"
-
- .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
- = render "projects/merge_requests/show/mr_box"
-
- - if @merge_request.source_branch_exists?
- = render "projects/merge_requests/show/how_to_merge"
-
- :javascript
- window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
-
- #js-vue-mr-widget.mr-widget
-
- - content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'vue_merge_request_widget'
-
- .content-block.content-block-small.emoji-list-container
- = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
-
- .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
- .merge-request-tabs-container
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- .nav-links.scrolling-tabs
- %ul.merge-request-tabs
- %li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
- Discussion
- %span.badge= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
- Commits
- %span.badge= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
- Pipelines
- %span.badge= @pipelines.size
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
- Changes
- %span.badge= @merge_request.diff_size
- #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
- %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
- %div
- .line-resolve-all{ "v-show" => "discussionCount > 0",
- ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
- %span.line-resolve-btn.is-disabled{ type: "button",
- ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
- = render "shared/icons/icon_status_success.svg"
- %span.line-resolve-text
- {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
- = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
- = render "discussions/jump_to_next"
-
- .tab-content#diff-notes-app
- #notes.notes.tab-pane.voting_notes
- .row
- %section.col-md-12
- .issuable-discussion
- = render "projects/merge_requests/discussion"
-
- #commits.commits.tab-pane
- -# This tab is always loaded via AJAX
- #pipelines.pipelines.tab-pane
- - if @pipelines.any?
- = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- #diffs.diffs.tab-pane
- -# This tab is always loaded via AJAX
-
- .mr-loading-status
- = spinner
-
-= render 'shared/issuable/sidebar', issuable: @merge_request
-- if @merge_request.can_be_reverted?(current_user)
- = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
-- if @merge_request.can_be_cherry_picked?
- = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
-
-:javascript
- $(function () {
- window.mergeRequest = new MergeRequest({
- action: "#{controller.action_name}"
- });
- });
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index 51d59280be8..454bc359b6b 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -3,15 +3,15 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('merge_conflicts')
= page_specific_javascript_tag('lib/ace.js')
-= render "projects/merge_requests/show/mr_title"
+= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details
- = render "projects/merge_requests/show/mr_box"
+ = render "projects/merge_requests/mr_box"
= render 'shared/issuable/sidebar', issuable: @merge_request
-#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json),
- resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } }
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+ resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
.loading{ "v-if" => "isLoading" }
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
index e675e1830d0..13026b7566a 100644
--- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
@@ -13,4 +13,4 @@
%button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
%span {{commitButtonText}}
.col-xs-6.text-right
- = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel"
+ = link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "btn btn-cancel"
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
new file mode 100644
index 00000000000..454bc359b6b
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -0,0 +1,38 @@
+- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('merge_conflicts')
+ = page_specific_javascript_tag('lib/ace.js')
+= render "projects/merge_requests/mr_title"
+
+.merge-request-details.issuable-details
+ = render "projects/merge_requests/mr_box"
+
+= render 'shared/issuable/sidebar', issuable: @merge_request
+
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+ resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
+ .loading{ "v-if" => "isLoading" }
+ %i.fa.fa-spinner.fa-spin
+
+ .nothing-here-block{ "v-if" => "hasError" }
+ {{conflictsData.errorMessage}}
+
+ = render partial: "projects/merge_requests/conflicts/commit_stats"
+
+ .files-wrapper{ "v-if" => "!isLoading && !hasError" }
+ .files
+ .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
+ .js-file-title.file-title
+ %i.fa.fa-fw{ ":class" => "file.iconClass" }
+ %strong {{file.filePath}}
+ = render partial: 'projects/merge_requests/conflicts/file_actions'
+ .diff-content.diff-wrap-lines
+ .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
+ .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ %parallel-conflict-lines{ ":file" => "file" }
+ %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" }
+ = render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
+
+ = render partial: "projects/merge_requests/conflicts/submit_form"
diff --git a/app/views/projects/merge_requests/_new_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml
index 627fc4e9671..627fc4e9671 100644
--- a/app/views/projects/merge_requests/_new_diffs.html.haml
+++ b/app/views/projects/merge_requests/creations/_diffs.html.haml
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 0f37abb579c..4e5aae496b1 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
New Merge Request
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
@@ -69,7 +69,7 @@
:javascript
new Compare({
- targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
- sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
- targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}"
+ targetProjectUrl: "#{project_new_merge_request_update_branches_path(@source_project)}",
+ sourceBranchUrl: "#{project_new_merge_request_branch_from_path(@source_project)}",
+ targetBranchUrl: "#{project_new_merge_request_branch_to_path(@source_project)}"
});
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index e3ecbee5490..c72dd1d8e29 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -31,28 +31,27 @@
%span.badge= @commits.size
- if @pipelines.any?
%li.builds-tab
- = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
+ = link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
Pipelines
%span.badge= @pipelines.size
%li.diffs-tab
- = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
+ = link_to url_for(params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @merge_request.diff_size
.tab-content
#commits.commits.tab-pane.active
- = render "projects/merge_requests/show/commits"
+ = render "projects/merge_requests/commits"
#diffs.diffs.tab-pane
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
- = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
+ = render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true
.mr-loading-status
= spinner
:javascript
var merge_request = new MergeRequest({
- action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
- setUrl: false,
+ action: "#{j params[:tab].presence || 'new'}",
});
diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/creations/branch_from.html.haml
index 3837c4b388d..3837c4b388d 100644
--- a/app/views/projects/merge_requests/branch_from.html.haml
+++ b/app/views/projects/merge_requests/creations/branch_from.html.haml
diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/creations/branch_to.html.haml
index d69b71790a0..d69b71790a0 100644
--- a/app/views/projects/merge_requests/branch_to.html.haml
+++ b/app/views/projects/merge_requests/creations/branch_to.html.haml
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
index 2e798ce780a..2e798ce780a 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/creations/new.html.haml
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/creations/update_branches.html.haml
index 64482973a89..64482973a89 100644
--- a/app/views/projects/merge_requests/update_branches.html.haml
+++ b/app/views/projects/merge_requests/creations/update_branches.html.haml
diff --git a/app/views/projects/merge_requests/diffs.html.haml b/app/views/projects/merge_requests/diffs.html.haml
deleted file mode 100644
index 2a5b8b1441e..00000000000
--- a/app/views/projects/merge_requests/diffs.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render "show"
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index 7f0913ea516..fb31e2fef00 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -1,5 +1,5 @@
- if @merge_request_diff.collected? || @merge_request_diff.overflow?
- = render 'projects/merge_requests/show/versions'
+ = render 'projects/merge_requests/diffs/versions'
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/diffs/_versions.html.haml
index 0999b95c9c9..9f7152b9824 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/diffs/_versions.html.haml
@@ -77,7 +77,7 @@
= icon('info-circle')
Selected versions have different base commits.
Changes will include
- = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
= succeed '.' do
@@ -94,4 +94,4 @@
of the diff.
.pull-right
- = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
+ = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 6d75a9f34a3..bfeb746ee83 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
+- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests"
- unless @project.default_issues_tracker?
@@ -10,6 +12,9 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
+- if show_new_nav?
+ - content_for :breadcrumbs_extra do
+ = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'projects/last_push'
@@ -17,13 +22,8 @@
%div{ class: container_class }
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- .nav-controls
- - if @can_bulk_update
- = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- - if merge_project
- = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do
- New merge request
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'shared/issuable/search_bar', type: :merge_requests
@@ -33,4 +33,4 @@
.merge-requests-holder
= render 'merge_requests'
- else
- = render 'shared/empty_states/merge_requests', button_path: new_namespace_project_merge_request_path(@project.namespace, @project)
+ = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index a00d3128ffe..6df19d6438b 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,8 +1,8 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
.merge-request
- = render "projects/merge_requests/show/mr_title"
- = render "projects/merge_requests/show/mr_box"
+ = render "projects/merge_requests/mr_title"
+ = render "projects/merge_requests/mr_box"
.alert.alert-danger
%p
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 2a5b8b1441e..13012151349 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -1 +1,97 @@
-= render "show"
+- @content_class = "limit-container-width" unless fluid_layout
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
+- page_description @merge_request.description
+- page_card_attributes @merge_request.card_attributes
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('diff_notes')
+
+.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
+ = render "projects/merge_requests/mr_title"
+
+ .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
+ = render "projects/merge_requests/mr_box"
+
+ - if @merge_request.source_branch_exists?
+ = render "projects/merge_requests/how_to_merge"
+
+ :javascript
+ window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
+
+ #js-vue-mr-widget.mr-widget
+
+ - content_for :page_specific_javascripts do
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'vue_merge_request_widget'
+
+ .content-block.content-block-small.emoji-list-container
+ = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
+
+ .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
+ .merge-request-tabs-container
+ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ .nav-links.scrolling-tabs
+ %ul.merge-request-tabs
+ %li.notes-tab
+ = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
+ Discussion
+ %span.badge= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ Commits
+ %span.badge= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ Pipelines
+ %span.badge= @pipelines.size
+ %li.diffs-tab
+ = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ Changes
+ %span.badge= @merge_request.diff_size
+ #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
+ %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
+ %div
+ .line-resolve-all{ "v-show" => "discussionCount > 0",
+ ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
+ %span.line-resolve-btn.is-disabled{ type: "button",
+ ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
+ = render "shared/icons/icon_status_success.svg"
+ %span.line-resolve-text
+ {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
+ = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
+ = render "discussions/jump_to_next"
+
+ .tab-content#diff-notes-app
+ #notes.notes.tab-pane.voting_notes
+ .row
+ %section.col-md-12
+ .issuable-discussion
+ = render "projects/merge_requests/discussion"
+
+ #commits.commits.tab-pane
+ -# This tab is always loaded via AJAX
+ #pipelines.pipelines.tab-pane
+ - if @pipelines.any?
+ = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
+ #diffs.diffs.tab-pane
+ -# This tab is always loaded via AJAX
+
+ .mr-loading-status
+ = spinner
+
+= render 'shared/issuable/sidebar', issuable: @merge_request
+- if @merge_request.can_be_reverted?(current_user)
+ = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_cherry_picked?
+ = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
+
+:javascript
+ $(function () {
+ window.mergeRequest = new MergeRequest({
+ action: "#{j params[:tab].presence || 'show'}",
+ });
+ });
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
deleted file mode 100644
index d9428b8562e..00000000000
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-- if @merge_request.closed_without_fork?
- .alert.alert-danger
- %p The source project of this merge request has been removed.
-
-.clearfix.detail-page-header
- .issuable-header
- .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
- = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
- %span.hidden-xs
- = @merge_request.state_human_name
-
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
-
- .issuable-meta
- = issuable_meta(@merge_request, @project, "Merge request")
-
- - if can?(current_user, :update_merge_request, @merge_request)
- .issuable-actions
- .clearfix.issue-btn-group.dropdown
- %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- Options
- = icon('caret-down')
- .dropdown-menu.dropdown-menu-align-right.hidden-lg
- %ul
- %li{ class: merge_request_button_visibility(@merge_request, true) }
- = 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'
- %li
- = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
- Edit
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 9a95b2a82ff..2e74b1b83cb 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -19,7 +19,7 @@
.form-actions
- if @milestone.new_record?
= f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
- else
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
+ = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 77b566db6b6..bc82b45f902 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,5 +1,5 @@
= render 'shared/milestones/milestone',
- milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
- issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
- merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ milestone_path: project_milestone_path(milestone.project, milestone),
+ issues_path: project_issues_path(milestone.project, milestone_title: milestone.title),
+ merge_requests_path: project_merge_requests_path(milestone.project, milestone_title: milestone.title),
milestone: milestone
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index e1096bd1d67..e53fcd6e425 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -9,7 +9,7 @@
.nav-controls
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
+ = link_to new_project_milestone_path(@project), class: 'btn btn-new', title: 'New milestone' do
New milestone
.milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4b692aba11c..0bf0e11c107 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -23,14 +23,14 @@
.milestone-buttons
- if can?(current_user, :admin_milestone, @project)
- if @milestone.active?
- = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
+ = link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
- else
- = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
+ = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
+ = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped btn-nr" do
Edit
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+ = link_to project_milestone_path(@project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
Delete
%a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index ed6077f6c6b..e8c26636be9 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -6,7 +6,7 @@
%div{ class: container_class }
.project-network
.controls
- = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success' do
= icon('search')
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index 1cf286ddc40..ba5845877e5 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -9,12 +9,12 @@
%hr
.no-repo-actions
- = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do
+ = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
#{ _('Create empty bare repository') }
%strong.prepend-left-10.append-right-10 or
- = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do
+ = link_to new_project_import_path(@project), class: 'btn' do
#{ _('Import repository') }
- if can? current_user, :remove_project, @project
diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml
index 42d9ef5ccba..7d6c30b7f8d 100644
--- a/app/views/projects/pages/_destroy.haml
+++ b/app/views/projects/pages/_destroy.haml
@@ -7,6 +7,6 @@
%p
Removing the pages will prevent from exposing them to outside world.
.form-actions
- = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
+ = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else
.nothing-here-block Only the project owner can remove pages
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 4f2dd1a1398..a85cda407af 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -6,8 +6,8 @@
- @domains.each do |domain|
%li
.pull-right
- = link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+ = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix
%span= link_to domain.domain, domain.url
%p
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index b22a54d75c8..098b0ef56ef 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -5,7 +5,7 @@
Pages
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
- = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do
+ = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do
%i.fa.fa-plus
New Domain
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index fc7fa5c1876..857ae00d0ab 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -24,6 +24,14 @@
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.col-md-9
+ %label.label-light
+ #{ s_('PipelineSchedules|Variables') }
+ %ul.js-pipeline-variable-list.pipeline-variable-list
+ - @schedule.variables.each do |variable|
+ = render 'variable_row', id: variable.id, key: variable.key, value: variable.value
+ = render 'variable_row'
+ .form-group
+ .col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
%div
= f.check_box :active, required: false, value: @schedule.active?
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 966d6cd8495..97c0407a01d 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -9,7 +9,7 @@
%td
- if pipeline_schedule.last_pipeline
.status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" }
- = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline_schedule.last_pipeline.id) do
+ = link_to project_pipeline_path(@project, pipeline_schedule.last_pipeline.id) do
= ci_icon_for_status(pipeline_schedule.last_pipeline.status)
%span ##{pipeline_schedule.last_pipeline.id}
- else
@@ -26,7 +26,7 @@
= pipeline_schedule.owner&.name
%td
.pull-right.btn-group
- - if can?(current_user, :update_pipeline_schedule, @project) && !pipeline_schedule.owned_by?(current_user)
+ - if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
diff --git a/app/views/projects/pipeline_schedules/_variable_row.html.haml b/app/views/projects/pipeline_schedules/_variable_row.html.haml
new file mode 100644
index 00000000000..564cb5d1ca9
--- /dev/null
+++ b/app/views/projects/pipeline_schedules/_variable_row.html.haml
@@ -0,0 +1,17 @@
+- id = local_assigns.fetch(:id, nil)
+- key = local_assigns.fetch(:key, "")
+- value = local_assigns.fetch(:value, "")
+%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
+ .pipeline-variable-row-body
+ %input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
+ %input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
+ %input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
+ name: "schedule[variables_attributes][][key]",
+ value: key,
+ placeholder: s_('PipelineSchedules|Input variable key') }
+ %textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
+ name: "schedule[variables_attributes][][value]",
+ placeholder: s_('PipelineSchedules|Input variable value') }
+ = value
+ %button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
+ %i.fa.fa-minus-circle{ 'aria-hidden': "true" }
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index c296152e54f..05fe80e5fed 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -13,7 +13,7 @@
= render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
.nav-controls
- = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do
+ = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
%span= _('New schedule')
- if @schedules.present?
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index d2f0cb0806f..ee2f236cec4 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -29,6 +29,6 @@
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do
- = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
%span
Charts
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 673c3370b62..f5149306734 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -26,10 +26,10 @@
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha js-details-short"
+ = link_to @commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha js-details-short"
= link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
%span.text-expander
\...
%span.js-details-content.hide
- = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha commit-hash-full"
+ = link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 85550e8fd32..ad61f033a1c 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -3,15 +3,15 @@
.tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom
%li.js-pipeline-tab-link
- = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
+ = link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
Pipeline
%li.js-builds-tab-link
- = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
+ = link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
Jobs
%span.badge.js-builds-counter= pipeline.statuses.count
- if failed_builds.present?
%li.js-failures-tab-link
- = link_to failures_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
+ = link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
Failed Jobs
%span.badge.js-failures-counter= failed_builds.count
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 8ffddfe6154..78002e8cd64 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title "Charts", "Pipelines"
+- page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
@@ -8,7 +8,7 @@
%div{ class: container_class }
.sub-header-block
.oneline
- A collection of graphs for Continuous Integration
+ = _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts
.row
diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml
index 93083397d5b..66786c7ff59 100644
--- a/app/views/projects/pipelines/charts/_overall.haml
+++ b/app/views/projects/pipelines/charts/_overall.haml
@@ -1,15 +1,15 @@
-%h4 Overall stats
+%h4= s_("PipelineCharts|Overall statistics")
%ul
%li
- Total:
- %strong= pluralize @counts[:total], 'pipeline'
+ = s_("PipelineCharts|Total:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li
- Successful:
- %strong= pluralize @counts[:success], 'pipeline'
+ = s_("PipelineCharts|Successful:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li
- Failed:
- %strong= pluralize @counts[:failed], 'pipeline'
+ = s_("PipelineCharts|Failed:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li
- Success ratio:
+ = s_("PipelineCharts|Success ratio:")
%strong
#{success_ratio(@counts)}%
diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml
index aee7c5492aa..1292f580a81 100644
--- a/app/views/projects/pipelines/charts/_pipeline_times.haml
+++ b/app/views/projects/pipelines/charts/_pipeline_times.haml
@@ -1,6 +1,6 @@
%div
%p.light
- Commit duration in minutes for last 30 commits
+ = _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 }
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index b6f453b9736..be884448087 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,29 +1,29 @@
-%h4 Pipelines charts
+%h4= _("Pipelines charts")
%p
&nbsp;
%span.cgreen
= icon("circle")
- success
+ = s_("Pipeline|success")
&nbsp;
%span.cgray
= icon("circle")
- all
+ = s_("Pipeline|all")
.prepend-top-default
%p.light
- Jobs for last week
+ = _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 }
.prepend-top-default
%p.light
- Jobs for last month
+ = _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 }
.prepend-top-default
%p.light
- Jobs for last year
+ = _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope|
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 38237d2d97d..c1729850cf4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -2,10 +2,10 @@
- page_title "Pipelines"
= render "projects/pipelines/head"
-#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json),
+#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
"css-class" => container_class,
"help-page-path" => help_page_path('ci/quick_start/README'),
- "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project),
+ "new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"all-path" => project_pipelines_path(@project),
"pending-path" => project_pipelines_path(@project, scope: :pending),
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 71a8e490c3e..308f2611e02 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -4,7 +4,7 @@
New Pipeline
%hr
-= form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
+= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
= form_errors(@pipeline)
.form-group
= f.label :ref, 'Create for', class: 'control-label'
@@ -17,7 +17,7 @@
.help-block Existing branch name, tag
.form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel'
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index b39453a50fb..63f85fc69a2 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -8,7 +8,7 @@
= render "projects/pipelines/with_tabs", pipeline: @pipeline
-.js-pipeline-details-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } }
+.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 580129ca809..255d7ef38e0 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -3,7 +3,7 @@
%h4.prepend-top-0
Pipelines
.col-lg-8
- = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
+ = form_for @project, url: project_pipelines_settings_path(@project) do |f|
%fieldset.builds-feature
- unless @repository.gitlab_ci_yml
.form-group
@@ -47,6 +47,14 @@
%hr
.form-group
+ = f.label :ci_config_path, 'Custom CI config path', class: 'label-light'
+ = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
+ %p.help-block
+ The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>
+ = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
+
+ %hr
+ .form-group
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
deleted file mode 100644
index c7996077bc7..00000000000
--- a/app/views/projects/project_members/_group_members.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-.panel.panel-default
- .panel-heading
- Group members with access to
- %strong= @group.name
- %span.badge= members.size
- - if can?(current_user, :admin_group_member, @group)
- .controls
- = link_to 'Manage group members',
- group_group_members_path(@group),
- class: 'btn'
- %ul.content-list
- = render partial: 'shared/members/member',
- collection: members.limit(20),
- as: :member,
- locals: { show_controls: false }
- - if members.size > 20
- %li
- and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 8bf2246662a..bf5b11ea30c 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,6 +1,6 @@
.row
.col-sm-12
- = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
+ = form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
= label_tag :user_ids, "Select members to invite", class: "label-light"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
@@ -18,4 +18,4 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
= f.submit "Add to project", class: "btn btn-create"
- = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default", title: "Import members from another project"
+ = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml
index 643569db646..c10ef648a8f 100644
--- a/app/views/projects/project_members/_new_shared_group.html.haml
+++ b/app/views/projects/project_members/_new_shared_group.html.haml
@@ -1,6 +1,6 @@
.row
.col-sm-12
- = form_tag namespace_project_group_links_path(@project.namespace, @project), class: 'js-requires-input', method: :post do
+ = form_tag project_group_links_path(@project), class: 'js-requires-input', method: :post do
.form-group
= label_tag :link_group_id, "Select a group to share with", class: "label-light"
= groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, class: "input-clamp", required: true)
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
deleted file mode 100644
index 7902ddb1ae9..00000000000
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- @project_group_links.each do |group_links|
- - shared_group = group_links.group
- - shared_group_members = shared_group.members
- - shared_group_users_count = shared_group_members.size
- .panel.panel-default
- .panel-heading
- Shared with
- %strong= shared_group.name
- group, members with
- %strong= group_links.human_access
- role (#{shared_group_users_count})
- - if can?(current_user, :admin_group, shared_group)
- .panel-head-actions
- = link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
- %i.fa.fa-pencil-square-o
- Edit group members
- %ul.content-list
- = render partial: 'shared/members/member',
- collection: shared_group_members.order(access_level: :desc).limit(20),
- as: :member,
- locals: { show_controls: false, show_roles: false }
- - if shared_group_users_count > 20
- %li
- and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)}
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 7b1a26043e1..e71d58ec26d 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -5,11 +5,11 @@
%strong
#{@project.name}
%span.badge= @project_members.total_count
- = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ = form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
= render 'shared/members/sort_dropdown'
- %ul.content-list
+ %ul.content-list.members-list
= render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 42ce4f8001b..f6ca8d5a921 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -5,11 +5,11 @@
%p.light
Only project members will be imported. Group members will be skipped.
%hr
-= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
+= form_tag apply_import_project_project_members_path(@project), method: 'post', class: 'form-horizontal' do
.form-group
= label_tag :source_project_id, "Project", class: 'control-label'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
= button_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", namespace_project_settings_members_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/_index.html.haml b/app/views/projects/project_members/index.html.haml
index fa99610c0be..25153fd0b6f 100644
--- a/app/views/projects/project_members/_index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,6 +1,8 @@
+- page_title "Members"
+
.row.prepend-top-default
- .col-lg-4.settings-sidebar
- %h4.prepend-top-0
+ .col-lg-12
+ %h4
Project members
- if can?(current_user, :admin_project_member, @project)
%p
@@ -13,7 +15,6 @@
%i Masters
or
%i Owners
- .col-lg-8
.light
- if can?(current_user, :admin_project_member, @project)
%ul.nav-links.project-member-tabs{ role: 'tablist' }
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml
index 27896272733..98793d632e6 100644
--- a/app/views/projects/protected_branches/_matching_branch.html.haml
+++ b/app/views/projects/protected_branches/_matching_branch.html.haml
@@ -6,5 +6,5 @@
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_branch.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0f80de94392..e4dadc42cc0 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,4 +1,4 @@
-%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
+%tr.js-protected-branch-edit-form{ data: { url: project_protected_branch_path(@project, protected_branch) } }
%td
%span.ref-name= protected_branch.name
@@ -7,10 +7,10 @@
%td
- if protected_branch.wildcard?
- matching_branches = protected_branch.matching(repository.branches)
- = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+ = link_to pluralize(matching_branches.count, "matching branch"), project_protected_branch_path(@project, protected_branch)
- else
- if commit = protected_branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
- else
(branch was removed from repository)
diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/_matching_tag.html.haml
index f17353df122..05f102d1ca3 100644
--- a/app/views/projects/protected_tags/_matching_tag.html.haml
+++ b/app/views/projects/protected_tags/_matching_tag.html.haml
@@ -6,5 +6,5 @@
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_tag.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml
index f11ce0483a9..5162da5e429 100644
--- a/app/views/projects/protected_tags/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_protected_tag.html.haml
@@ -1,4 +1,4 @@
-%tr.js-protected-tag-edit-form{ data: { url: namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) } }
+%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } }
%td
%span.ref-name= protected_tag.name
@@ -7,10 +7,10 @@
%td
- if protected_tag.wildcard?
- matching_tags = protected_tag.matching(repository.tags)
- = link_to pluralize(matching_tags.count, "matching tag"), namespace_project_protected_tag_path(@project.namespace, @project, protected_tag)
+ = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag)
- else
- if commit = protected_tag.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
- else
(tag was removed from repository)
diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml
index dcdc432b654..a0535edafc3 100644
--- a/app/views/projects/registry/repositories/_image.html.haml
+++ b/app/views/projects/registry/repositories/_image.html.haml
@@ -8,7 +8,7 @@
- if can?(current_user, :update_container_image, @project)
.controls.hidden-xs.pull-right
- = link_to namespace_project_container_registry_path(@project.namespace, @project, image),
+ = link_to project_container_registry_path(@project, image),
class: 'btn btn-remove has-tooltip',
title: 'Remove repository',
data: { confirm: 'Are you sure?' },
@@ -30,4 +30,3 @@
= render partial: 'tag', collection: image.tags
- else
.nothing-here-block No tags in Container Registry for this container image.
-
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index 378a23f07e6..0b082a2137f 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -25,7 +25,7 @@
- if can?(current_user, :update_container_image, @project)
%td.content
.controls.hidden-xs.pull-right
- = link_to namespace_project_registry_repository_tag_path(@project.namespace, @project, tag.repository, tag.name),
+ = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name),
method: :delete,
class: 'btn btn-remove has-tooltip',
title: 'Remove tag',
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 93ee9382a6e..0a5a38a3694 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -10,11 +10,11 @@
%strong= @tag.name
- = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
+ = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'shared/notes/hints'
.error-alert
.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
- = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
+ = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
index 17b9fecfeb1..6d083c5c516 100644
--- a/app/views/projects/remove_fork.js.haml
+++ b/app/views/projects/remove_fork.js.haml
@@ -1,2 +1,2 @@
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index d9c39fb87b7..170f9e259df 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,7 +1,7 @@
- commit = update
%tr
%td
- = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do
+ = link_to project_commits_path(@project, commit.head.name) do
%strong
= commit.head.name
- if @project.root_ref?(commit.head.name)
@@ -9,7 +9,7 @@
%td
%div
- = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
+ = link_to project_commits_path(@project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author)
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 674f87e8220..abc97bcdff5 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -9,7 +9,7 @@
= icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
%small
- = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+ = link_to edit_project_runner_path(@project, runner) do
%i.fa.fa-edit.btn
- else
%span.commit-sha
@@ -21,7 +21,7 @@
= link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
- runner_project = @project.runner_projects.find_by(runner_id: runner)
- = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ = link_to 'Disable for this project', project_runner_project_path(@project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific?
= form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 0671dd66e78..a4e820628f3 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -9,10 +9,10 @@
on GitLab.com).
%hr
- if @project.shared_runners_enabled?
- = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
+ = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do
Disable shared Runners
- else
- = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
+ = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do
Enable shared Runners
&nbsp; for this project
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 6dffc026392..b842fd57cf3 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -9,20 +9,21 @@
%p= @service.description
.col-lg-9
- = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_namespace_project_service_path } }) do |form|
+ = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- .footer-block.row-content-block
- %button.btn.btn-save{ type: 'submit' }
- = icon('spinner spin', class: 'hidden js-btn-spinner')
- %span.js-btn-label
- Save changes
- &nbsp;
- - if @service.valid? && @service.activated?
- - unless @service.can_test?
- - disabled_class = 'disabled'
- - disabled_title = @service.disabled_title
+ - if @service.editable?
+ .footer-block.row-content-block
+ %button.btn.btn-save{ type: 'submit' }
+ = icon('spinner spin', class: 'hidden js-btn-spinner')
+ %span.js-btn-label
+ Save changes
+ &nbsp;
+ - if @service.valid? && @service.activated?
+ - unless @service.can_test?
+ - disabled_class = 'disabled'
+ - disabled_title = @service.disabled_title
- = link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 997b702da33..915c6b22162 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -21,7 +21,7 @@
%td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") }
= boolean_to_icon service.activated?
%td
- = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ = link_to edit_project_service_path(@project, service.to_param) do
%strong= service.title
%td.hidden-xs
= service.description
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index fcc91be11cd..44c0b7a90dc 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -2,6 +2,6 @@
- unless @service.activated?
.row
.col-sm-9.col-sm-offset-3
- = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
+ = link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15)
Add to Mattermost
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index c4ac384ca1a..0996ec06ab7 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -11,7 +11,7 @@
= link_to 'More information', '#'
.col-lg-9
- .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{namespace_project_prometheus_active_metrics_path(@project.namespace, @project, :json)}" } }
+ .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } }
.panel-heading
%h3.panel-title
Monitored
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index 00bd563999f..15ba09b10ba 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -9,26 +9,22 @@
= link_to edit_project_path(@project), title: 'General' do
%span
General
- = nav_link(controller: :members) do
- = link_to project_settings_members_path(@project), title: 'Members' do
- %span
- Members
- if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
Integrations
= nav_link(controller: :repository) do
- = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
%span
Repository
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
- = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do
+ = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
%span
Pipelines
- if Gitlab.config.pages.enabled
= nav_link(controller: :pages) do
- = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+ = link_to project_pages_path(@project), title: 'Pages' do
%span
Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 00ccc3ec41e..6afb38c5709 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -3,6 +3,6 @@
= render "projects/settings/head"
= render 'projects/runners/index'
-= render 'projects/variables/index'
+= render 'ci/variables/index'
= render 'projects/triggers/index'
= render 'projects/pipelines_settings/show'
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index a6640592dba..00700e286c3 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -9,8 +9,8 @@
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
- = link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
- = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
- = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+ = link_to "Edit", edit_project_hook_path(@project, hook), class: "btn btn-sm"
+ = link_to "Test", test_project_hook_path(@project, hook), class: "btn btn-sm"
+ = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index ed34f5c0520..39f8cb9a0e0 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name} activity"
-xml.link href: namespace_project_url(@project.namespace, @project, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id namespace_project_url(@project.namespace, @project)
+xml.link href: project_url(@project, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link href: project_url(@project), rel: "alternate", type: "text/html"
+xml.id project_url(@project)
xml.updated @events[0].updated_at.xmlschema if @events[0]
xml << render(@events) if @events.any?
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 7447197ed89..d413c4619be 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,9 +1,10 @@
- @no_container = true
+- flash_message_container = show_new_nav? ? :new_global_flash : :flash_message
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity")
+ = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
-= content_for :flash_message do
+= content_for flash_message_container do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
@@ -16,16 +17,16 @@
%nav.project-stats{ class: container_class }
%ul.nav
%li
- = link_to project_files_path(@project) do
+ = link_to project_tree_path(@project) do
#{_('Files')} (#{storage_counter(@project.statistics.total_repository_size)})
%li
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ = link_to project_commits_path(@project, current_ref) do
#{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
- %l
- = link_to namespace_project_branches_path(@project.namespace, @project) do
+ %li
+ = link_to project_branches_path(@project) do
#{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = link_to project_tags_path(@project) do
#{n_('Tag', 'Tags', @repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme
@@ -73,7 +74,7 @@
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do
#{ _('Set up auto deploy') }
-%div{ class: container_class }
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if @project.archived?
.text-warning.center.prepend-top-20
%p
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 34ee4ff1937..f09871c7fcc 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -2,16 +2,16 @@
.hidden-xs
- if can?(current_user, :update_project_snippet, @snippet)
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do
+ = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
Edit
- if can?(current_user, :update_project_snippet, @snippet)
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
+ = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
+ = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam_by?(current_user)
- = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -21,16 +21,16 @@
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
+ = link_to new_project_snippet_path(@project), title: "New snippet" do
New snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_project_snippet, @snippet)
%li
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ = link_to edit_project_snippet_path(@project, @snippet) do
Edit
- if @snippet.submittable_as_spam_by?(current_user)
%li
- = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
+ = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 24b92094b7d..d41cc8e0425 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
Edit Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
+= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 84e05cd6d88..4f8ce526c83 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -7,13 +7,13 @@
.nav-controls.hidden-xs
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do
+ = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" do
New snippet
- if can?(current_user, :create_project_snippet, @project)
.visible-xs
&nbsp;
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do
+ = link_to new_project_snippet_path(@project), class: "btn btn-new btn-block", title: "New snippet" do
New snippet
= render 'snippets/snippets'
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index cfed3a79bc5..d3e6b456f48 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
+= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 44cb734d7b9..468ab922542 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,7 +2,7 @@
- release = @releases.find { |release| release.tag == tag.name }
%li.flex-row
.row-main-content.str-truncated
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'item-title ref-name' do
+ = link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
= icon('tag')
= tag.name
@@ -29,9 +29,9 @@
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
+ = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 56656ea3d86..bf97cbc1f68 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -24,7 +24,7 @@
%li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :push_code, @project)
- = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+ = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
New tag
.tags
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 52af295bddd..f1bbaf40387 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -39,7 +39,7 @@
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
:javascript
window.gl = window.gl || { };
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 2b81ce4b9fa..d02cd70f4c3 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -19,17 +19,17 @@
.nav-controls.controls-flex
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
+ = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
+ = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
+ = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
= icon('history')
.btn-container.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.btn-container.controls-item-full
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
- if @tag.message.present?
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 17b9fecfeb1..6d083c5c516 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,2 +1,2 @@
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 425b460eb09..fd8175e1e01 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -2,7 +2,7 @@
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
+ = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
%span.str-truncated= file_name
%td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index f9147815427..4579a912f39 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -2,8 +2,8 @@
%article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
+ = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
%strong
= readme.name
- = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
+ = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: project_blob_path(@project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 84da16b6bb1..f3d4706809f 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
%span.str-truncated
- = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
+ = link_to_gfm commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 7854e1305db..6560bd5ab3f 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -10,7 +10,7 @@
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
- = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
+ = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
%td
%td.hidden-xs
@@ -20,7 +20,7 @@
= render "projects/tree/readme", readme: tree.readme
- if can_edit_tree?
- = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
:javascript
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 00da76349da..858418ff8df 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -4,11 +4,11 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
- path_breadcrumbs do |title, path|
%li
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- if current_user
%li
@@ -23,7 +23,7 @@
%ul.dropdown-menu
- if can_edit_tree?
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
+ = link_to project_new_blob_path(@project, @id) do
= icon('pencil fw')
#{ _('New file') }
%li
@@ -36,10 +36,10 @@
#{ _('New directory') }
- elsif can?(current_user, :fork_project, @project)
%li
- - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
+ - continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('pencil fw')
@@ -48,7 +48,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
@@ -57,7 +57,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('folder fw')
@@ -65,17 +65,17 @@
%li.divider
%li
- = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') }
%li
- = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') }
.tree-controls
= render 'projects/find_file_link'
- = link_to s_('Commits|History'), namespace_project_commits_path(@project.namespace, @project, @id), class: 'btn'
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 15c9536133c..0c9c8750f2c 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -2,7 +2,7 @@
%td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(tree_item)
- = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
+ = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do
%span.str-truncated= path
%td.hidden-xs.tree-commit
%td.tree-time-ago.text-right
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 96a08f9f8be..130980556c1 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -2,10 +2,9 @@
- page_title @path.presence || _("Files"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render "projects/commits/head"
-= render 'projects/last_push'
-
-%div{ class: container_class }
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+ = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 9b5f63ae81a..6249c32b7cc 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -33,10 +33,10 @@
- take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
- revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
- if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
- = link_to 'Take ownership', take_ownership_namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
+ = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
- if can?(current_user, :admin_trigger, trigger)
- = link_to edit_namespace_project_trigger_path(@project.namespace, @project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
+ = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
%i.fa.fa-pencil
- if can?(current_user, :manage_trigger, trigger)
- = link_to namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
+ = link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
%i.fa.fa-trash
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index dcf1f767bf7..2c05ebe52ae 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,6 @@
- if @project.valid?
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index 297a53ca98c..df533952b76 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -1,9 +1 @@
-- page_title "Variables"
-
-.row.prepend-top-default.append-bottom-default
- .col-lg-3
- = render "content"
- .col-lg-9
- %h5.prepend-top-0
- Update variable
- = render "form", btn_text: "Save variable"
+= render 'ci/variables/show'
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index c10b3004bc3..fc6b7a33943 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -12,7 +12,7 @@
.form-group
.col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-12
- = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do
+ = render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'shared/notes/hints'
@@ -36,8 +36,8 @@
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
.pull-right
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
+ = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped"
- else
= f.submit 'Create page', class: "btn-create btn"
.pull-right
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
+ = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 6a578dbf640..3bbd8042c3a 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -2,8 +2,8 @@
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
- if can?(current_user, :create_wiki, @project) && @page.latest?
- = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do
+ = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
Edit
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 1e553940593..13dd8461433 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -9,7 +9,7 @@
.form-group
= label_tag :new_wiki_path do
%span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
%span.new-wiki-page-slug-tip
= icon('lightbulb-o')
Tip: You can specify the full path for the new file.
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index 6298cf6c8da..7c2f562d422 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -1,5 +1,5 @@
%li
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index c2f9e65015d..62873d3aa66 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -3,7 +3,7 @@
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
- - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project)
+ - git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
@@ -15,7 +15,7 @@
= render @sidebar_wiki_entries, context: 'sidebar'
.block
- = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
+ = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
More Pages
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
index 0a61d90177b..2423ac6abce 100644
--- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -1,3 +1,3 @@
%li{ class: active_when(params[:id] == wiki_page.slug) }
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+ = link_to project_wiki_path(@project, wiki_page) do
= wiki_page.title.capitalize
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index fbe192a40ec..df0ec14eb3b 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -8,7 +8,7 @@
.nav-text
%h2.wiki-page-title
- if @page.persisted?
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ = link_to @page.title.capitalize, project_wiki_path(@project, @page)
- else
= @page.title.capitalize
%span.light
@@ -23,10 +23,10 @@
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
- if @page.persisted?
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
- if can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
+ = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete
= render 'form'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 0e47e2a5fa3..306feeff259 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -6,7 +6,7 @@
.nav-text
%h2.wiki-page-title
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ = link_to @page.title.capitalize, project_wiki_path(@project, @page)
%span.light
&middot;
History
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 5fba2b1a5ae..dece1fad0bb 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -9,7 +9,7 @@
Wiki Pages
.nav-controls
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do
+ = link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
Clone repository
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index f003ff6b63f..13591dd8e74 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -22,7 +22,7 @@
- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
+ You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}.
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 7f1f807e2e7..de473c23d66 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -3,7 +3,7 @@
.file-holder
.js-file-title.file-title
- ref = @search_results.repository_ref
- - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
+ - blob_link = project_blob_path(@project, tree_join(ref, file_name))
= link_to blob_link do
%i.fa.fa-file
%strong
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 026f404ce07..aef825691e0 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -11,7 +11,7 @@
%small.pull-right.cgray
- if snippet_title.project_id?
- = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
+ = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project)
.snippet-info
= snippet_title.to_reference
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index d87f9df2677..16a0e432d62 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -2,7 +2,7 @@
.blob-result
.file-holder
.js-file-title.file-title
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do
+ = link_to project_wiki_path(@project, wiki_blob.basename) do
%i.fa.fa-file
%strong
= wiki_blob.basename
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 1d4fd71522d..435acbc634c 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -5,21 +5,21 @@
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0
- %li
+ %li.issuable-mr.hidden-xs
= image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
= issuable_mr
- if upvotes > 0
- %li
+ %li.issuable-upvotes.hidden-xs
= icon('thumbs-up')
= upvotes
- if downvotes > 0
- %li
+ %li.issuable-downvotes.hidden-xs
= icon('thumbs-down')
= downvotes
-%li
+%li.issuable-comments.hidden-xs
= link_to issuable_url, class: ('no-comments' if note_count.zero?) do
= icon('comments')
= note_count
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 3a49227961f..49555b6ff4e 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,6 +1,6 @@
- if @issues.to_a.any?
.panel.panel-default.panel-small.panel-without-border
- %ul.content-list.issues-list
+ %ul.content-list.issues-list.issuable-list
= render partial: 'projects/issues/issue', collection: @issues
= paginate @issues, theme: "gitlab"
- else
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index de0281e97c6..2f776a17f45 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -23,7 +23,7 @@
- if can_subscribe_to_label_in_different_levels?(label)
%a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
%span Unsubscribe
- %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
%span Subscribe at project level
%a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
%span Subscribe at group level
@@ -56,7 +56,7 @@
= icon('chevron-down')
%ul.dropdown-menu
%li
- %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
Project level
%a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
Group level
@@ -66,7 +66,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
- = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
+ = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 7b599dff0e3..7f58298c60f 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,7 +2,7 @@
- if can?(current_user, :admin_label, @project)
.draggable-handler
= icon('bars')
- .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
+ .js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label),
dom_id: dom_id(label), type: label.type } }
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' }
= icon('star-o')
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index eecbb32e90e..0517896cfbd 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,6 +1,6 @@
- if @merge_requests.to_a.any?
.panel.panel-default.panel-small.panel-without-border
- %ul.content-list.mr-list
+ %ul.content-list.mr-list.issuable-list
= render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index aa93572bf94..dff847159d3 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -6,7 +6,7 @@
- status_klass = "ci-status-icon ci-status-icon-#{detailed_status.group}"
.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_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } }
+ %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)
= icon('caret-down')
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index d52bb6b4dd7..4498c8f8349 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,12 +1,12 @@
- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
+= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags")
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index b200e5fc528..7ca14ac93cc 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -7,10 +7,11 @@
= markdown @service.help
.service-settings
- .form-group
- = form.label :active, "Active", class: "control-label"
- .col-sm-10
- = form.check_box :active
+ - if @service.show_active_box?
+ .form-group
+ = form.label :active, "Active", class: "control-label"
+ .col-sm-10
+ = form.check_box :active
- if @service.supported_events.present?
.form-group
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index a212c714826..785a500e44e 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,3 +1,5 @@
+- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
+
.dropdown.inline.prepend-left-10
%button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } }
- if @sort.present?
@@ -23,7 +25,7 @@
= sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
= sort_title_milestone_later
- - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+ - if viewing_issues
= link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
= sort_title_due_date_soon
= link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index 5e2f4cf109d..bfda522f2f6 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -7,5 +7,5 @@
%h4 Labels can be applied to issues and merge requests to categorize them.
%p You can also star a label to make it a priority label.
- if can?(current_user, :admin_label, @project)
- = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
- = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
+ = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
+ = link_to 'Generate a default set of labels', generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
diff --git a/app/views/shared/icons/_add_new_group.svg b/app/views/shared/icons/_add_new_group.svg
new file mode 100644
index 00000000000..ecd52c5e99f
--- /dev/null
+++ b/app/views/shared/icons/_add_new_group.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#E1DBF2" fill-rule="nonzero" d="M59.65 32.65H60l-2-2.42-2 2.4-2-2.4-2 2.4-2-2.4-2 2.4-2-2.4-2 2.42h.77C45.57 34.6 46 36.75 46 39c0 2.84-.7 5.5-1.92 7.86 1.97 2.28 4.83 3.64 7.92 3.64 5.8 0 10.5-4.74 10.5-10.6 0-2.8-1.08-5.36-2.85-7.25zM43.18 29.6c2.4-2.1 5.52-3.3 8.82-3.3 7.46 0 13.5 6.1 13.5 13.6S59.46 53.5 52 53.5c-3.68 0-7.1-1.5-9.6-4.04C39.3 53.44 34.44 56 29 56c-9.4 0-17-7.6-17-17s7.6-17 17-17c3.22 0 6.23.9 8.8 2.45 2.13 1.3 3.97 3.05 5.38 5.16zM17 34c-.65 1.54-1 3.23-1 5 0 7.18 5.82 13 13 13s13-5.82 13-13c0-1.77-.35-3.46-1-5h-9c-.53 0-1.04-.2-1.4-.6L29 31.84l-1.6 1.58c-.36.4-.87.6-1.4.6h-9zm21.38-4c-2.4-2.5-5.76-4-9.38-4-3.62 0-6.98 1.5-9.38 4h5.55l2.42-2.4c.74-.8 2-.8 2.8 0l2.4 2.4h5.54z"/>
+ <path fill="#6B4FBB" d="M47.6 42.32c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zm8.8 0c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zM25 44h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-1c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_add_new_project.svg b/app/views/shared/icons/_add_new_project.svg
new file mode 100644
index 00000000000..3c1e15453df
--- /dev/null
+++ b/app/views/shared/icons/_add_new_project.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M30 24a4 4 0 0 0-4 4v22a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V28a4 4 0 0 0-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#FC6D26" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_add_new_user.svg b/app/views/shared/icons/_add_new_user.svg
new file mode 100644
index 00000000000..0ad40498d7b
--- /dev/null
+++ b/app/views/shared/icons/_add_new_user.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#E1DBF2" d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z"/>
+ <path fill="#E1DBF2" fill-rule="nonzero" d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z"/>
+ <path fill="#6B4FBB" d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_configure_server.svg b/app/views/shared/icons/_configure_server.svg
new file mode 100644
index 00000000000..b1137b7ec94
--- /dev/null
+++ b/app/views/shared/icons/_configure_server.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#FEE1D3" fill-rule="nonzero" d="M24.92 35.15c-1.72-1.4-1.98-3.9-.6-5.63l1.26-1.55c1.4-1.72 3.9-2 5.63-.6l.7.56c.7-.4 1.4-.73 2.1-1V26c0-2.2 1.8-4 4-4h2c2.2 0 4 1.8 4 4v.92c.8.28 1.5.62 2.1 1l.7-.55c1.7-1.4 4.3-1.12 5.7.6l1.3 1.55c1.4 1.72 1.2 4.23-.6 5.63l-.7.6c.3.74.4 1.5.5 2.3l.9.2c2.2.5 3.5 2.64 3 4.8L56.4 45c-.5 2.15-2.64 3.5-4.8 3l-.88-.2c-.44.63-.92 1.24-1.46 1.8l.4.82c.9 1.98.1 4.38-1.9 5.35l-1.8.87c-2 .97-4.37.15-5.34-1.84l-.46-.85c-.34.03-.74.05-1.13.05-.4 0-.8-.02-1.2-.05l-.4.85c-.95 2-3.34 2.8-5.33 1.84l-1.8-.87c-1.97-.97-2.8-3.37-1.83-5.35l.4-.8c-.54-.58-1.02-1.2-1.46-1.83l-.8.2c-2.2.5-4.3-.9-4.8-3l-.4-2c-.5-2.2.85-4.3 3-4.8l.9-.2c.1-.8.3-1.6.5-2.3l-.7-.6zm4.95.77c-.53 1.2-.83 2.47-.87 3.8-.02.9-.66 1.68-1.55 1.9l-2.32.53.45 1.94 2.3-.6c.9-.2 1.8.2 2.23 1 .7 1.1 1.5 2.2 2.5 3 .7.6.9 1.6.5 2.4l-1 2.1 1.8.9 1.1-2.1c.4-.8 1.3-1.3 2.2-1.1.7.1 1.3.2 2 .2s1.3-.1 2-.2c.9-.2 1.8.3 2.2 1.1l1 2.1 1.8-.9-1.2-2c-.4-.8-.2-1.8.5-2.4 1-.85 1.84-1.88 2.45-3.05.4-.82 1.33-1.24 2.2-1.04l2.33.54.45-1.95-2.32-.54c-.9-.2-1.52-.97-1.54-1.88-.03-1.4-.33-2.6-.86-3.8-.4-.9-.2-1.8.5-2.4l1.9-1.5-1.3-1.6-1.8 1.5c-.8.5-1.8.6-2.5 0-1.1-.8-2.3-1.4-3.5-1.7-.9-.2-1.5-1-1.5-1.9V26h-2v2.38c0 .9-.6 1.7-1.5 1.93-1.3.4-2.5 1-3.5 1.7-.8.6-1.8.6-2.5 0l-1.9-1.5-1.26 1.6 1.8 1.5c.7.6.94 1.6.6 2.4z"/>
+ <path fill="#FC6D26" fill-rule="nonzero" d="M39 46c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_globe.svg b/app/views/shared/icons/_globe.svg
new file mode 100644
index 00000000000..c2daae5f317
--- /dev/null
+++ b/app/views/shared/icons/_globe.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M30.24 27.823A14.98 14.98 0 0 0 24 40c0 2.549.636 4.949 1.757 7.051-.297-2.684.644-4.026 2.823-4.026 3.707 0 2.462 5.365 4.473 5.761 2.01.396 4.175.396 4.267 3.29.04 1.257-.265 2.157-.917 2.7a15.095 15.095 0 0 0 8.555-1.006c.035-1.91.303-4.941 2.21-5.61 2.373-.833-.55-1.431.734-3.368 1.17-1.762-3.297-5.2 0-4.832 3.477.388 5.044-.816 6.024-1.456a14.903 14.903 0 0 0-1.373-4.94c-.873.4-2.19.465-3.702-.538-.757-.502-1.084-3.944-2.107-3.944-3.823 0-4.065 3.17-5.994 3.944-1.076.431-4.193 3.773-5.614 3.596-1.126-.14-1.071-4.417-2.45-5.166-1.359-.738-2.174-1.948-2.447-3.633zM39 59c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19z"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 7cfdfb6e6ee..964fe5220f7 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -31,7 +31,7 @@
.title
Milestone
.filter-item
- = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
+ = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: "Milestone" } })
.block
.title
Labels
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
new file mode 100644
index 00000000000..8a1268a1c6d
--- /dev/null
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -0,0 +1,14 @@
+- is_current_user = issuable_author_is_current_user(issuable)
+- display_issuable_type = issuable_display_type(issuable)
+- button_method = issuable_close_reopen_button_method(issuable)
+
+- if can_update && is_current_user
+ = link_to "Close #{display_issuable_type}", close_issuable_url(issuable), method: button_method,
+ class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
+ = link_to "Reopen #{display_issuable_type}", reopen_issuable_url(issuable), method: button_method,
+ class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
+- elsif can_update && !is_current_user
+ = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
+- else
+ = link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
+ class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
new file mode 100644
index 00000000000..6756a7f17fd
--- /dev/null
+++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
@@ -0,0 +1,49 @@
+- display_issuable_type = issuable_display_type(issuable)
+- button_action = issuable.closed? ? 'reopen' : 'close'
+- display_button_action = button_action.capitalize
+- button_responsive_class = 'hidden-xs hidden-sm'
+- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button issuable-close-button"
+- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
+- button_method = issuable_close_reopen_button_method(issuable)
+
+.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
+ = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_url(issuable),
+ method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
+
+ = button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
+ data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
+ = icon('caret-down', class: 'toggle-icon icon')
+
+ %ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } }
+ %li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
+ data: { text: "Close #{display_issuable_type}", url: close_issuable_url(issuable),
+ button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title
+ Close
+ = display_issuable_type
+
+ %li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
+ data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_url(issuable),
+ button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title
+ Reopen
+ = display_issuable_type
+
+ %li.divider.droplab-item-ignore
+
+ %li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
+ button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title Report abuse
+ %p.text
+ Report
+ = display_issuable_type.pluralize
+ that are abusive, inappropriate or spam.
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 9a8529c6cbb..e8feff32d26 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -20,7 +20,7 @@
%a.dropdown-toggle-page{ href: "#" }
Create new label
%li
- = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+ = link_to project_labels_path(@project), :"data-is-link" => true do
- if show_create && @project && can?(current_user, :admin_label, @project)
Manage labels
- else
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 6750921338a..955b8866c2c 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -11,10 +11,10 @@
%ul.dropdown-footer-list
- if can? current_user, :admin_milestone, project
%li
- = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do
+ = link_to new_project_milestone_path(project), title: "New Milestone" do
Create new
%li
- = link_to namespace_project_milestones_path(project.namespace, project) do
+ = link_to project_milestones_path(project) do
- if can? current_user, :admin_milestone, project
Manage milestones
- else
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index d3d290692a2..bdb573cb8fd 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -19,11 +19,11 @@
content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do
.js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } }
- .filtered-search-box-input-container
+ .filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
- %input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
+ %input.form-control.filtered-search{ search_filter_input_options(type) }
= icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 745f1ee62da..ecbaa901792 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -37,13 +37,13 @@
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
- if issuable.milestone
- = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
+ = link_to issuable.milestone.title, project_milestone_path(@project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
- else
%span.no-value None
.selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
- = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
+ = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
- if issuable.has_attribute?(:time_estimate)
#issuable-time-tracker.block
// Fallback while content is loading
@@ -106,7 +106,7 @@
- selected_labels.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (project_labels_path(@project, :json) if @project) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index 2ea5eb960c0..57392cd7fbb 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -37,19 +37,20 @@
- issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
-
+ - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- title = 'Select assignee'
- if issuable.is_a?(Issue)
- unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
+ - dropdown_options = issue_assignees_dropdown_options
+ - title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true
- data['dropdown-title'] = title
- - data['dropdown-header'] = 'Assignee'
- - data['max-select'] = 1
+ - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
+ - data['max-select'] = dropdown_options[:data][:'max-select']
- options[:data].merge!(data)
= dropdown_tag(title, options: options)
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index bfa91629e1e..8f6509a8ce8 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -11,8 +11,7 @@
.col-sm-10.col-sm-offset-2
- if issuable.can_remove_source_branch?(current_user)
.checkbox
- - initial_checkbox_value = issuable.merge_params.key?('force_remove_source_branch') ? issuable.force_remove_source_branch? : true
= label_tag 'merge_request[force_remove_source_branch]' do
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
- = check_box_tag 'merge_request[force_remove_source_branch]', '1', initial_checkbox_value
+ = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
Remove source branch when merge request is accepted.
diff --git a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
index 77175c839a6..567cde764e2 100644
--- a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
@@ -7,5 +7,5 @@
- if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
- = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
+ = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml
index d97fdf179d7..40224cec9e8 100644
--- a/app/views/shared/members/_access_request_buttons.html.haml
+++ b/app/views/shared/members/_access_request_buttons.html.haml
@@ -1,18 +1,20 @@
- model_name = source.model_name.to_s.downcase
-.project-action-button.inline
- - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+ .project-action-button.inline
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
= link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(source) },
class: 'btn'
- - elsif requester = source.requesters.find_by(user_id: current_user.id)
+- elsif requester = source.requesters.find_by(user_id: current_user.id)
+ .project-action-button.inline
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
method: :delete,
data: { confirm: remove_member_message(requester) },
class: 'btn'
- - elsif source.request_access_enabled && can?(current_user, :request_access, source)
+- elsif source.request_access_enabled && can?(current_user, :request_access, source)
+ .project-action-button.inline
= link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
method: :post,
class: 'btn'
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 1d5a61cffce..bcdad3c153a 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -14,7 +14,7 @@
%span{ class: ('text-warning' if group_link.expires_soon?) }
Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
.controls.member-controls
- = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
+ = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
= hidden_field_tag "group_link[group_access]", group_link.group_access
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
@@ -36,7 +36,7 @@
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
%i.clear-icon.js-clear-input
- if can_admin_member
- = link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
+ = link_to project_group_link_path(@project, group_link),
method: :delete,
data: { confirm: "Are you sure you want to remove #{group.name}?" },
class: 'btn btn-remove prepend-left-10' do
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index a5aa768b1b2..951b4dd7b36 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,5 +1,6 @@
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
+- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
@@ -8,45 +9,53 @@
%span.list-item-name
- if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
- %strong
- = link_to user.name, user_path(user)
- %span.cgray= user.to_reference
+ .user-info
+ = link_to user.name, user_path(user), class: 'member'
+ %span.cgray= user.to_reference
- - if user == current_user
- %span.label.label-success.prepend-left-5 It's you
+ - if user == current_user
+ %span.label.label-success.prepend-left-5 It's you
- - if user.blocked?
- %label.label.label-danger
- %strong Blocked
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
- - if source.instance_of?(Group) && source != @group
- &middot;
- = link_to source.full_name, source, class: "member-group-link"
+ - if source.instance_of?(Group) && source != @group
+ &middot;
+ = link_to source.full_name, source, class: "member-group-link"
- .hidden-xs.cgray
- - if member.request?
- Requested
- = time_ago_with_tooltip(member.requested_at)
- - else
- Joined #{time_ago_with_tooltip(member.created_at)}
- - if member.expires?
- ·
- %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
- Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
+ .cgray
+ - if member.request?
+ Requested
+ = time_ago_with_tooltip(member.requested_at)
+ - else
+ Joined #{time_ago_with_tooltip(member.created_at)}
+ - if member.expires?
+ ·
+ %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
+ Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: ''
- %strong= member.invite_email
- .cgray
- Invited
- - if member.created_by
- by
- = link_to member.created_by.name, user_path(member.created_by)
- = time_ago_with_tooltip(member.created_at)
+ .user-info
+ .member= member.invite_email
+ .cgray
+ Invited
+ - if member.created_by
+ by
+ = link_to member.created_by.name, user_path(member.created_by)
+ = time_ago_with_tooltip(member.created_at)
- if show_roles
- current_resource = @project || @group
.controls.member-controls
- if show_controls && member.source == current_resource
+
+ - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
+ = link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
+ method: :post,
+ class: 'btn btn-default prepend-left-10 hidden-xs',
+ title: 'Resend invite'
+
- if user != current_user && can_admin_member
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
@@ -75,13 +84,17 @@
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
method: :post,
- class: 'btn btn-default prepend-left-10'
+ class: 'btn btn-default prepend-left-10 visible-xs-block'
- elsif member.request? && can_admin_member
- = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
+ = link_to polymorphic_path([:approve_access_request, member]),
method: :post,
class: 'btn btn-success prepend-left-10',
- title: 'Grant access'
+ title: 'Grant access' do
+ %span{ class: ('visible-xs-block' unless force_mobile_view) }
+ Grant access
+ - unless force_mobile_view
+ = icon('check inverse', class: 'hidden-xs')
- if can?(current_user, action_member_permission(:destroy, member), member)
- if current_user == user
@@ -96,8 +109,9 @@
data: { confirm: remove_member_message(member) },
class: 'btn btn-remove prepend-left-10',
title: remove_member_title(member) do
- %span.visible-xs-block
+ %span{ class: ('visible-xs-block' unless force_mobile_view) }
Delete
- = icon('trash', class: 'hidden-xs')
+ - unless force_mobile_view
+ = icon('trash', class: 'hidden-xs')
- else
%span.member-access-text= member.human_access
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 92f6e7428ae..09b9944082f 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,8 +1,10 @@
+- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
+
- if requesters.any?
- .panel.panel-default.prepend-top-default
+ .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
.panel-heading
Users requesting access to
%strong= membership_source.name
%span.badge= requesters.size
- %ul.content-list
- = render partial: 'shared/members/member', collection: requesters, as: :member
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index a7c67ac9980..3739f4c221d 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -1,14 +1,13 @@
-# @project is present when viewing Project's milestone
- project = @project || issuable.project
- namespace = @project_namespace || project.namespace.becomes(Namespace)
+- labels = issuable.labels
- assignees = issuable.assignees
-- issuable_type = issuable.class.table_name
- base_url_args = [namespace, project]
-- issuable_type_args = base_url_args + [issuable_type]
+- issuable_type_args = base_url_args + [issuable.class.table_name]
- issuable_url_args = base_url_args + [issuable]
-- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable)
-%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable_url_args) }
+%li.issuable-row
%span
- if show_project_name
%strong #{project.name} &middot;
@@ -18,10 +17,10 @@
= confidential_icon(issuable)
= link_to issuable.title, issuable_url_args, title: issuable.title
.issuable-detail
- = link_to [project.namespace.becomes(Namespace), project, issuable] do
+ = link_to [namespace, project, issuable] do
%span.issuable-number= issuable.to_reference
- - issuable.labels.each do |label|
+ - labels.each do |label|
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 680e1f3a4ea..6f6a036b13f 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -1,10 +1,15 @@
- dashboard = local_assigns[:dashboard]
-- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
+- custom_dom_id = dom_id(milestone.try(:milestones) ? milestone.milestones.first : milestone)
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
.col-sm-6
%strong= link_to truncate(milestone.title, length: 100), milestone_path
+ - if milestone.is_group_milestone?
+ %span - Group Milestone
+ - else
+ %span - Project Milestone
+
.col-sm-6
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
@@ -13,31 +18,37 @@
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
- - if milestone.is_a?(GlobalMilestone)
+ - if milestone.is_a?(GlobalMilestone) || milestone.is_group_milestone?
.row
.col-sm-6
- .expiration= render('shared/milestone_expired', milestone: milestone)
- .projects
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+ - if milestone.is_legacy_group_milestone?
+ .expiration= render('shared/milestone_expired', milestone: milestone)
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = dashboard ? milestone.project.name_with_namespace : milestone.project.name
- if @group
- .col-sm-6
+ .col-sm-6.milestone-actions
- if can?(current_user, :admin_milestones, @group)
+ - if milestone.is_group_milestone?
+ = link_to edit_group_milestone_path(@group, milestone.id), class: "btn btn-xs btn-grouped" do
+ Edit
+ \
- if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+ = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-xs btn-grouped btn-close"
- if @project
.row
- .col-sm-6= render('shared/milestone_expired', milestone: milestone)
+ .col-sm-6
+ = render('shared/milestone_expired', milestone: milestone)
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
+ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit
\
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
+ = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
+ = link_to project_milestone_path(milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
Delete
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 9bb87640319..895fb8247b5 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -21,7 +21,7 @@
.title
Start date
- if @project && can?(current_user, :admin_milestone, @project)
- = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+ = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
.value
%span.value-content
- if milestone.start_date
@@ -51,7 +51,7 @@
.title.hide-collapsed
Due date
- if @project && can?(current_user, :admin_milestone, @project)
- = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+ = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
.value.hide-collapsed
%span.value-content
- if milestone.due_date
@@ -73,7 +73,7 @@
Issues
%span.badge= milestone.issues_visible_to_user(current_user).count
- if project && can?(current_user, :create_issue, project)
- = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
+ = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
New issue
.value.hide-collapsed.bold
%span.milestone-stat
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 4de8a6cb15f..b95a4ea674d 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,8 +1,10 @@
+- issues_accessible = milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
+
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.js-milestone-tabs
- - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
+ - if issues_accessible
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
@@ -25,13 +27,14 @@
Labels
%span.badge= milestone.labels.count
+- issues = milestone.sorted_issues(current_user)
- show_project_name = local_assigns.fetch(:show_project_name, false)
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content
- - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
- .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } }
- = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ - if issues_accessible
+ .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
+ = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
-# loaded async
= render "shared/milestones/tab_loading"
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 2562f085338..b93837e3087 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -22,39 +22,55 @@
- if group
.pull-right
- if can?(current_user, :admin_milestones, group)
+ - if milestone.is_group_milestone?
+ = link_to edit_group_milestone_path(group, milestone.iid), class: "btn btn btn-grouped" do
+ Edit
- if milestone.active?
- = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
- = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.detail-page-description.milestone-detail
%h2.title
= markdown_field(milestone, :title)
+ - if @milestone.is_group_milestone? && @milestone.description.present?
+ %div
+ .description
+ .wiki
+ = markdown_field(@milestone, :description)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - milestone.milestones.each do |ms|
- %tr
- %td
- - project_name = group ? ms.project.name : ms.project.name_with_namespace
- = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
- %td
- = ms.issues_visible_to_user(current_user).opened.count
- %td
- - if ms.closed?
- Closed
- - else
- Open
- %td
- = ms.expires_at
+- if @milestone.is_legacy_group_milestone? || @milestone.is_dashboard_milestone?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - milestone.milestones.each do |ms|
+ %tr
+ %td
+ - project_name = group ? ms.project.name : ms.project.name_with_namespace
+ = link_to project_name, project_milestone_path(ms.project, ms)
+ %td
+ = ms.issues_visible_to_user(current_user).opened.count
+ %td
+ - if ms.closed?
+ Closed
+ - else
+ Open
+ %td
+ = ms.expires_at
+- elsif @milestone.is_group_milestone?
+ %br
+ View
+ = link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
+ or
+ = link_to 'Merge Requests', merge_requests_group_path(@group, milestone_title: milestone.title)
+ in this milestone
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 29cf5825292..1dfe380db16 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,6 +1,6 @@
- noteable_name = @note.noteable.human_class_name
-.pull-left.btn-group.append-right-10.comment-type-dropdown.js-comment-type-dropdown
+.pull-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
%input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
- if @note.can_be_discussion_note?
@@ -9,8 +9,8 @@
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
- %a{ href: '#' }
- = icon('check')
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
.description
%strong Comment
%p
@@ -19,8 +19,8 @@
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
- %a{ href: '#' }
- = icon('check')
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
.description
%strong Start discussion
%p
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 1e34b7c1e76..7174855e176 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -60,6 +60,6 @@
= link_to note.attachment.url, target: '_blank' do
= icon('paperclip')
= note.attachment_identifier
- = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
+ = link_to delete_attachment_project_note_path(note.project, note),
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index aaffc0927eb..7ed6c622558 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -13,7 +13,7 @@
- if projects.any?
%ul.projects-list
- projects.each_with_index do |project, i|
- - css_class = (i >= projects_limit) ? 'hide' : nil
+ - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 8c3d6351ac2..4bdbc26a4c3 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -31,7 +31,7 @@
- if show_last_commit_as_description
.description.prepend-top-5
- = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
+ = link_to_gfm project.commit.title, project_commit_path(project, project.commit),
class: "commit-row-message"
- elsif project.description.present?
.description.prepend-top-5
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 8549cb91b03..43322978749 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -36,6 +36,6 @@
= f.submit 'Save changes', class: "btn-save btn"
- if @snippet.project_id
- = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 5d2d2317f22..7388f20a9fd 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -30,7 +30,7 @@
- if link_project && snippet.project_id?
%span.hidden-xs
in
- = link_to namespace_project_path(snippet.project.namespace, snippet.project) do
+ = link_to project_path(snippet.project) do
= snippet.project.name_with_namespace
.pull-right.snippet-updated-at
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index d1e88274878..805a346a85e 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -12,7 +12,7 @@
- if event.push?
#{event.action_name} #{event.ref_type}
%strong
- - commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
+ - commits_path = project_commits_path(event.project, event.ref_name)
= link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
- else
= event_action_name(event)
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index e85e221d353..45ce49bb5c0 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -2,18 +2,34 @@ class BackgroundMigrationWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
- # Schedules a number of jobs in bulk
+ # Enqueues a number of jobs in bulk.
#
# The `jobs` argument should be an Array of Arrays, each sub-array must be in
# the form:
#
# [migration-class, [arg1, arg2, ...]]
- def self.perform_bulk(*jobs)
+ def self.perform_bulk(jobs)
Sidekiq::Client.push_bulk('class' => self,
'queue' => sidekiq_options['queue'],
'args' => jobs)
end
+ # Schedules multiple jobs in bulk, with a delay.
+ #
+ def self.perform_bulk_in(delay, jobs)
+ now = Time.now.to_i
+ schedule = now + delay.to_i
+
+ if schedule <= now
+ raise ArgumentError, 'The schedule time must be in the future!'
+ end
+
+ Sidekiq::Client.push_bulk('class' => self,
+ 'queue' => sidekiq_options['queue'],
+ 'args' => jobs,
+ 'at' => schedule)
+ end
+
# Performs the background migration.
#
# See Gitlab::BackgroundMigration.perform for more information.
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index 08e281e7350..e383202260d 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -18,18 +18,10 @@ class ExpireJobCacheWorker
private
def project_pipeline_path(project, pipeline)
- Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
- project.namespace,
- project,
- pipeline,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
end
def project_job_path(project, job)
- Gitlab::Routing.url_helpers.namespace_project_build_path(
- project.namespace,
- project,
- job.id,
- format: :json)
+ Gitlab::Routing.url_helpers.project_build_path(project, job.id, format: :json)
end
end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index d760f5b140f..7c02d6cf892 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -23,42 +23,24 @@ class ExpirePipelineCacheWorker
private
def project_pipelines_path(project)
- Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
- project.namespace,
- project,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
end
def project_pipeline_path(project, pipeline)
- Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
- project.namespace,
- project,
- pipeline,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
end
def commit_pipelines_path(project, commit)
- Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- commit.id,
- format: :json)
+ Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json)
end
def new_merge_request_pipelines_path(project)
- Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
- project.namespace,
- project,
- format: :json)
+ Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
end
def each_pipelines_merge_request_path(project, pipeline)
pipeline.all_merge_requests.each do |merge_request|
- path = Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
- project.namespace,
- project,
- merge_request,
- format: :json)
+ path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json)
yield(path)
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 89286595ca6..b8f8d3750d9 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -2,11 +2,11 @@ class PostReceive
include Sidekiq::Worker
include DedicatedSidekiqQueue
- def perform(project_identifier, identifier, changes)
- project, is_wiki = parse_project_identifier(project_identifier)
+ def perform(gl_repository, identifier, changes)
+ project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
if project.nil?
- log("Triggered hook for non-existing project with identifier \"#{project_identifier}\"")
+ log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"")
return false
end
@@ -59,21 +59,6 @@ class PostReceive
# Nothing defined here yet.
end
- # To maintain backwards compatibility, we accept both gl_repository or
- # repository paths as project identifiers. Our plan is to migrate to
- # gl_repository only with the following plan:
- # 9.2: Handle both possible values. Keep Gitlab-Shell sending only repo paths
- # 9.3 (or patch release): Make GitLab Shell pass gl_repository if present
- # 9.4 (or patch release): Make GitLab Shell always pass gl_repository
- # 9.5 (or patch release): Handle only gl_repository as project identifier on this method
- def parse_project_identifier(project_identifier)
- if project_identifier.start_with?('/')
- Gitlab::RepoPath.parse(project_identifier)
- else
- Gitlab::GlRepository.parse(project_identifier)
- end
- end
-
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index ae8c980c9e4..8b0cfcc8af8 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -45,7 +45,7 @@ class StuckCiJobsWorker
def search(status, timeout)
builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago)
- builds.joins(:project).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
+ builds.joins(:project).merge(Project.without_deleted).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
yield(build)
end
end
diff --git a/changelogs/unreleased/18000-remember-me-for-oauth-login.yml b/changelogs/unreleased/18000-remember-me-for-oauth-login.yml
new file mode 100644
index 00000000000..1ef92756a76
--- /dev/null
+++ b/changelogs/unreleased/18000-remember-me-for-oauth-login.yml
@@ -0,0 +1,4 @@
+---
+title: Honor the "Remember me" parameter for OAuth-based login
+merge_request: 11963
+author:
diff --git a/changelogs/unreleased/20628-add-oauth-implicit-grant.yml b/changelogs/unreleased/20628-add-oauth-implicit-grant.yml
new file mode 100644
index 00000000000..58a28142feb
--- /dev/null
+++ b/changelogs/unreleased/20628-add-oauth-implicit-grant.yml
@@ -0,0 +1,4 @@
+---
+title: "#20628 Enable implicit grant in GitLab as OAuth Provider"
+merge_request: 12384
+author: Mateusz Pytel
diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
new file mode 100644
index 00000000000..a5f78202c93
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/new-project.feature' spinach with rspec
+merge_request: 12550
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-snippets-spinach.yml b/changelogs/unreleased/23036-replace-snippets-spinach.yml
new file mode 100644
index 00000000000..545805b1302
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-snippets-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'snippets/snippets.feature' spinach with rspec
+merge_request: 12385
+author: Alexander Randa @randaalex
diff --git a/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
new file mode 100644
index 00000000000..442406c3c04
--- /dev/null
+++ b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
@@ -0,0 +1,4 @@
+---
+title: Allow creation of files and directories with spaces through Web UI
+merge_request: 12608
+author:
diff --git a/changelogs/unreleased/2501-ce-port-update-welcome-page.yml b/changelogs/unreleased/2501-ce-port-update-welcome-page.yml
new file mode 100644
index 00000000000..cac8a522308
--- /dev/null
+++ b/changelogs/unreleased/2501-ce-port-update-welcome-page.yml
@@ -0,0 +1,4 @@
+---
+title: Update welcome page UX for new users
+merge_request: 12662
+author:
diff --git a/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml b/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml
new file mode 100644
index 00000000000..6688e79588f
--- /dev/null
+++ b/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml
@@ -0,0 +1,4 @@
+---
+title: Improve members view on mobile
+merge_request: 12619
+author:
diff --git a/changelogs/unreleased/26125-match-username-on-search.yml b/changelogs/unreleased/26125-match-username-on-search.yml
new file mode 100644
index 00000000000..74e918bec16
--- /dev/null
+++ b/changelogs/unreleased/26125-match-username-on-search.yml
@@ -0,0 +1,5 @@
+---
+title: Inserts exact matches of name, username and email to the top of the search
+ list
+merge_request: 12525
+author:
diff --git a/changelogs/unreleased/29893-change-menu-locations.yml b/changelogs/unreleased/29893-change-menu-locations.yml
new file mode 100644
index 00000000000..d348adc2d74
--- /dev/null
+++ b/changelogs/unreleased/29893-change-menu-locations.yml
@@ -0,0 +1,3 @@
+---
+title: Moved "Members in a project" menu entry and path locations
+merge_request: 11560
diff --git a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
new file mode 100644
index 00000000000..83ce3fb4d0a
--- /dev/null
+++ b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
@@ -0,0 +1,4 @@
+---
+title: Removes deleted_at and pending_delete occurrences in Project related queries
+merge_request: 12091
+author:
diff --git a/changelogs/unreleased/31982-liberation-mono-linux.yml b/changelogs/unreleased/31982-liberation-mono-linux.yml
new file mode 100644
index 00000000000..c0f29cf4c47
--- /dev/null
+++ b/changelogs/unreleased/31982-liberation-mono-linux.yml
@@ -0,0 +1,4 @@
+---
+title: Change order of monospace fonts to fix bug on some linux distros
+merge_request:
+author:
diff --git a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
new file mode 100644
index 00000000000..99e64b9b467
--- /dev/null
+++ b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
@@ -0,0 +1,4 @@
+---
+title: Fix spacing on runner buttons.
+merge_request: !12535
+author:
diff --git a/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
new file mode 100644
index 00000000000..ebb27d118d7
--- /dev/null
+++ b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admins to disable all restricted visibility levels
+merge_request: 12649
+author:
diff --git a/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
new file mode 100644
index 00000000000..7784d7d0ce0
--- /dev/null
+++ b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
@@ -0,0 +1,4 @@
+---
+title: Allow customize CI config path
+merge_request: 12509
+author: Keith Pope
diff --git a/changelogs/unreleased/32838-admin-panel-spacing.yml b/changelogs/unreleased/32838-admin-panel-spacing.yml
new file mode 100644
index 00000000000..ccd703fa43f
--- /dev/null
+++ b/changelogs/unreleased/32838-admin-panel-spacing.yml
@@ -0,0 +1,4 @@
+---
+title: Add wells to admin dashboard overview to fix spacing problems
+merge_request:
+author:
diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
new file mode 100644
index 00000000000..d3172c405c3
--- /dev/null
+++ b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
@@ -0,0 +1,4 @@
+---
+title: Use authorize_update_pipeline_schedule in PipelineSchedulesController
+merge_request: 11846
+author:
diff --git a/changelogs/unreleased/33130-remove-group-modal.yml b/changelogs/unreleased/33130-remove-group-modal.yml
new file mode 100644
index 00000000000..4672d41ded5
--- /dev/null
+++ b/changelogs/unreleased/33130-remove-group-modal.yml
@@ -0,0 +1,4 @@
+---
+title: "Remove group modal like remove project modal (requires typing + confirmation)"
+merge_request: 12569
+author: Diego Souza
diff --git a/changelogs/unreleased/33360-generate-kubeconfig.yml b/changelogs/unreleased/33360-generate-kubeconfig.yml
new file mode 100644
index 00000000000..96f0b1bc93f
--- /dev/null
+++ b/changelogs/unreleased/33360-generate-kubeconfig.yml
@@ -0,0 +1,4 @@
+---
+title: Provide KUBECONFIG from KubernetesService for runners
+merge_request: 12223
+author:
diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
new file mode 100644
index 00000000000..d6b1b2524c6
--- /dev/null
+++ b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
+merge_request: 12514
+author: Huang Tao
diff --git a/changelogs/unreleased/33580-fix-api-scoping.yml b/changelogs/unreleased/33580-fix-api-scoping.yml
new file mode 100644
index 00000000000..f4ebb13c082
--- /dev/null
+++ b/changelogs/unreleased/33580-fix-api-scoping.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API Scoping
+merge_request: 12300
+author:
diff --git a/changelogs/unreleased/33657-user-projects-api.yml b/changelogs/unreleased/33657-user-projects-api.yml
new file mode 100644
index 00000000000..a8d485865e9
--- /dev/null
+++ b/changelogs/unreleased/33657-user-projects-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add user projects API
+merge_request: 12596
+author: Ivan Chernov
diff --git a/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml b/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml
new file mode 100644
index 00000000000..7402c33c5c6
--- /dev/null
+++ b/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml
@@ -0,0 +1,4 @@
+---
+title: Improve the performance of the project list API
+merge_request: 12679
+author:
diff --git a/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
new file mode 100644
index 00000000000..c2bce368a58
--- /dev/null
+++ b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
@@ -0,0 +1,4 @@
+---
+title: Introduce cache policies for CI jobs
+merge_request: 12483
+author:
diff --git a/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml b/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml
new file mode 100644
index 00000000000..810cc8489b5
--- /dev/null
+++ b/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to enable the performance bar per user or Feature group
+merge_request: 12362
+author:
diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
new file mode 100644
index 00000000000..69d5d34b072
--- /dev/null
+++ b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
@@ -0,0 +1,4 @@
+---
+title: Allow the feature flags to be enabled/disabled with more granularity
+merge_request: 12357
+author:
diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
new file mode 100644
index 00000000000..8f8b5a96c2b
--- /dev/null
+++ b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
@@ -0,0 +1,4 @@
+---
+title: Change milestone endpoint for groups
+merge_request: 12374
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
new file mode 100644
index 00000000000..a3ade8db214
--- /dev/null
+++ b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
@@ -0,0 +1,4 @@
+---
+title: Allow unauthenticated access to the /api/v4/users API
+merge_request: 12445
+author:
diff --git a/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
new file mode 100644
index 00000000000..1a631c3f0a4
--- /dev/null
+++ b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Simplified Chinese translations of Commits Page
+merge_request: 12405
+author: Huang Tao
diff --git a/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
new file mode 100644
index 00000000000..3cf7c0b547f
--- /dev/null
+++ b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Traditional Chinese in HongKong translations of Commits Page
+merge_request: 12406
+author: Huang Tao
diff --git a/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml b/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml
new file mode 100644
index 00000000000..224b9e1852f
--- /dev/null
+++ b/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Traditional Chinese in Taiwan translations of Commits Page
+merge_request: 12407
+author: Huang Tao
diff --git a/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
new file mode 100644
index 00000000000..b43a38f3794
--- /dev/null
+++ b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Esperanto translations of Commits Page
+merge_request: 12410
+author: Huang Tao
diff --git a/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
new file mode 100644
index 00000000000..9177ae3acd1
--- /dev/null
+++ b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Bulgarian translations of Commits Page
+merge_request: 12411
+author: Huang Tao
diff --git a/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml b/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml
deleted file mode 100644
index e7fff2c1a9f..00000000000
--- a/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: Fix optional arugments for POST :id/variables'
-merge_request: 12474
-author:
diff --git a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
new file mode 100644
index 00000000000..4911315d018
--- /dev/null
+++ b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
@@ -0,0 +1,4 @@
+---
+title: Closes any open Autocomplete of the markdown editor when the form is closed
+merge_request: 12521
+author:
diff --git a/changelogs/unreleased/34531-remove-scroll.yml b/changelogs/unreleased/34531-remove-scroll.yml
new file mode 100644
index 00000000000..c3c5289f66f
--- /dev/null
+++ b/changelogs/unreleased/34531-remove-scroll.yml
@@ -0,0 +1,4 @@
+---
+title: Update jobs page output to have a scrollable page
+merge_request: 12587
+author:
diff --git a/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
new file mode 100644
index 00000000000..31f4262c9f9
--- /dev/null
+++ b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
+merge_request: 12578
+author: Huang Tao
diff --git a/changelogs/unreleased/34578-sidebar-padding.yml b/changelogs/unreleased/34578-sidebar-padding.yml
new file mode 100644
index 00000000000..dc4647298e6
--- /dev/null
+++ b/changelogs/unreleased/34578-sidebar-padding.yml
@@ -0,0 +1,4 @@
+---
+title: fix left & right padding on sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml b/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml
new file mode 100644
index 00000000000..11c01d28dc2
--- /dev/null
+++ b/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Fix dashboard labels dropdown
+merge_request: 12708
+author:
diff --git a/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml b/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml
new file mode 100644
index 00000000000..736991318d7
--- /dev/null
+++ b/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml
@@ -0,0 +1,4 @@
+---
+title: Cleanup minor UX issues in the performance dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml b/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml
new file mode 100644
index 00000000000..c7a68935e8c
--- /dev/null
+++ b/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed the chart legend not being set correctly
+merge_request: 12628
+author:
diff --git a/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
new file mode 100644
index 00000000000..90a1f8c98fe
--- /dev/null
+++ b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Italian translations of Commits Page
+merge_request: 12645
+author: Huang Tao
diff --git a/changelogs/unreleased/34727-simplified-member-settings.yml b/changelogs/unreleased/34727-simplified-member-settings.yml
new file mode 100644
index 00000000000..8c4844c001b
--- /dev/null
+++ b/changelogs/unreleased/34727-simplified-member-settings.yml
@@ -0,0 +1,4 @@
+---
+title: Remove two columned layout from project member settings
+merge_request:
+author:
diff --git a/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml b/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml
new file mode 100644
index 00000000000..4fddabebf36
--- /dev/null
+++ b/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent bad data being added to application settings when Redis is unavailable
+merge_request: 12750
+author:
diff --git a/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml b/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml
new file mode 100644
index 00000000000..8df3a1a6940
--- /dev/null
+++ b/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml
@@ -0,0 +1,4 @@
+---
+title: N+1 problems on milestone page
+merge_request: 12670
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34810-vue-pagination.yml b/changelogs/unreleased/34810-vue-pagination.yml
new file mode 100644
index 00000000000..5cd03518a98
--- /dev/null
+++ b/changelogs/unreleased/34810-vue-pagination.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent disabled pagination button to be clicked
+merge_request:
+author:
diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml
new file mode 100644
index 00000000000..aeec6688425
--- /dev/null
+++ b/changelogs/unreleased/adam-external-issue-references-spike.yml
@@ -0,0 +1,4 @@
+---
+title: Improve support for external issue references
+merge_request: 12485
+author:
diff --git a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
new file mode 100644
index 00000000000..4948d415bed
--- /dev/null
+++ b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
@@ -0,0 +1,6 @@
+---
+title: Rename duplicated variables with the same key for projects. Add environment_scope
+ column to variables and add unique constraint to make sure that no variables could
+ be created with the same key within a project
+merge_request: 12363
+author:
diff --git a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
new file mode 100644
index 00000000000..f2591042e98
--- /dev/null
+++ b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add group members counting and plan related data on namespaces API
+merge_request:
+author:
diff --git a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
new file mode 100644
index 00000000000..f37f2fa94ae
--- /dev/null
+++ b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
@@ -0,0 +1,4 @@
+---
+title: Rename all reserved paths that could have been created
+merge_request: 11713
+author:
diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
new file mode 100644
index 00000000000..b359a25053a
--- /dev/null
+++ b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
new file mode 100644
index 00000000000..5fad7a0f883
--- /dev/null
+++ b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 'New merge request' button for users who don't have push access to canonical
+ project
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml
new file mode 100644
index 00000000000..c1a026e1f29
--- /dev/null
+++ b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and
+ blob paths
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-page-image-size.yml b/changelogs/unreleased/dm-page-image-size.yml
new file mode 100644
index 00000000000..b18c00470fc
--- /dev/null
+++ b/changelogs/unreleased/dm-page-image-size.yml
@@ -0,0 +1,4 @@
+---
+title: Limit OpenGraph image size to 64x64
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml b/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml
new file mode 100644
index 00000000000..8b026a4c289
--- /dev/null
+++ b/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml
@@ -0,0 +1,4 @@
+---
+title: Don't show auxiliary blob viewer for README when there is no wiki
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
new file mode 100644
index 00000000000..616241dd941
--- /dev/null
+++ b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
@@ -0,0 +1,4 @@
+---
+title: Strip trailing whitespace in relative submodule URL
+merge_request:
+author:
diff --git a/changelogs/unreleased/enable-polling-env.yml b/changelogs/unreleased/enable-polling-env.yml
new file mode 100644
index 00000000000..b3f65f02574
--- /dev/null
+++ b/changelogs/unreleased/enable-polling-env.yml
@@ -0,0 +1,4 @@
+---
+title: Re-enable realtime for environments table
+merge_request:
+author:
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
new file mode 100644
index 00000000000..d61c3b97d11
--- /dev/null
+++ b/changelogs/unreleased/enable-webpack-code-splitting.yml
@@ -0,0 +1,5 @@
+---
+title: Enable support for webpack code-splitting by dynamically setting publicPath
+ at runtime
+merge_request: 12032
+author:
diff --git a/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml b/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml
new file mode 100644
index 00000000000..333895ffba9
--- /dev/null
+++ b/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml
@@ -0,0 +1,4 @@
+---
+title: Add Group secret variables
+merge_request: 12582
+author:
diff --git a/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml b/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml
new file mode 100644
index 00000000000..d497575b7f3
--- /dev/null
+++ b/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml
@@ -0,0 +1,4 @@
+---
+title: Add variables to pipelines schedules
+merge_request: 12372
+author:
diff --git a/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
new file mode 100644
index 00000000000..bbcf2946ea7
--- /dev/null
+++ b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
@@ -0,0 +1,4 @@
+---
+title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
+merge_request: 11218
+author: Stefan Hanreich
diff --git a/changelogs/unreleased/feature-user-agent-details-api.yml b/changelogs/unreleased/feature-user-agent-details-api.yml
new file mode 100644
index 00000000000..839ec7d21cd
--- /dev/null
+++ b/changelogs/unreleased/feature-user-agent-details-api.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admins to retrieve user agent details for an issue or snippet
+merge_request: 12655
+author:
diff --git a/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml b/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml
new file mode 100644
index 00000000000..27ac50c6cc2
--- /dev/null
+++ b/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml
@@ -0,0 +1,4 @@
+---
+title: Add creation time filters to user search API for admins
+merge_request: 12682
+author:
diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml
new file mode 100644
index 00000000000..fc2cd18ddb6
--- /dev/null
+++ b/changelogs/unreleased/fix-assigned-issuable-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Add issuable-list class to shared mr/issue lists to fix new responsive layout
+ design
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml b/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml
deleted file mode 100644
index f12e7b53790..00000000000
--- a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix head pipeline stored in merge request for external pipelines
-merge_request: 12478
-author:
diff --git a/changelogs/unreleased/fix-mrs-merged-immediately.yml b/changelogs/unreleased/fix-mrs-merged-immediately.yml
new file mode 100644
index 00000000000..41c06614e6d
--- /dev/null
+++ b/changelogs/unreleased/fix-mrs-merged-immediately.yml
@@ -0,0 +1,4 @@
+---
+title: Don't mark empty MRs as merged on push to the target branch
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-runner_online_check.yml b/changelogs/unreleased/fix-runner_online_check.yml
new file mode 100644
index 00000000000..bc0de979b4c
--- /dev/null
+++ b/changelogs/unreleased/fix-runner_online_check.yml
@@ -0,0 +1,4 @@
+---
+title: Fix offline runner detection
+merge_request: 11751
+author: Alessio Caiazza
diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
new file mode 100644
index 00000000000..856990a6126
--- /dev/null
+++ b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed sidebar not collapsing on merge requests in mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/foreign-keys-for-project-model.yml b/changelogs/unreleased/foreign-keys-for-project-model.yml
new file mode 100644
index 00000000000..3648b1c3735
--- /dev/null
+++ b/changelogs/unreleased/foreign-keys-for-project-model.yml
@@ -0,0 +1,4 @@
+---
+title: Speed up project removals by adding foreign keys with cascading deletes to various tables
+merge_request:
+author:
diff --git a/changelogs/unreleased/gitaly-mandatory.yml b/changelogs/unreleased/gitaly-mandatory.yml
new file mode 100644
index 00000000000..c060e0add29
--- /dev/null
+++ b/changelogs/unreleased/gitaly-mandatory.yml
@@ -0,0 +1,4 @@
+---
+title: Remove option to disable Gitaly
+merge_request: 12677
+author:
diff --git a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
new file mode 100644
index 00000000000..ec2f4f9c3d8
--- /dev/null
+++ b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
@@ -0,0 +1,4 @@
+---
+title: Fix errors caused by attempts to report already blocked or deleted users
+merge_request: 12502
+author: Horacio Bertorello
diff --git a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
new file mode 100644
index 00000000000..3b465d84126
--- /dev/null
+++ b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Hide archived project labels from group issue tracker
+merge_request: 12547
+author: Horacio Bertorello
diff --git a/changelogs/unreleased/issue-description-gfm.yml b/changelogs/unreleased/issue-description-gfm.yml
new file mode 100644
index 00000000000..4d421bff677
--- /dev/null
+++ b/changelogs/unreleased/issue-description-gfm.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed GFM references not being included when updating issues inline
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue-form-multiple-line-markdown.yml b/changelogs/unreleased/issue-form-multiple-line-markdown.yml
deleted file mode 100644
index 23128f346bc..00000000000
--- a/changelogs/unreleased/issue-form-multiple-line-markdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed multi-line markdown tooltip buttons in issue edit form
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_30126_be.yml b/changelogs/unreleased/issue_30126_be.yml
new file mode 100644
index 00000000000..96bb8d9574b
--- /dev/null
+++ b/changelogs/unreleased/issue_30126_be.yml
@@ -0,0 +1,4 @@
+---
+title: Add native group milestones
+merge_request:
+author:
diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml
new file mode 100644
index 00000000000..d3d67d04574
--- /dev/null
+++ b/changelogs/unreleased/issueable-list-cleanup.yml
@@ -0,0 +1,4 @@
+---
+title: Clean up UI of issuable lists and make more responsive
+merge_request:
+author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
new file mode 100644
index 00000000000..f84d41b7929
--- /dev/null
+++ b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
@@ -0,0 +1,4 @@
+---
+title: Improve the overall UX for the new monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
new file mode 100644
index 00000000000..8a0e9ca855c
--- /dev/null
+++ b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-allow-force-repo-create.yml b/changelogs/unreleased/sh-allow-force-repo-create.yml
new file mode 100644
index 00000000000..2a65ba807bb
--- /dev/null
+++ b/changelogs/unreleased/sh-allow-force-repo-create.yml
@@ -0,0 +1,4 @@
+---
+title: Make Project#ensure_repository force create a repo
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml b/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml
deleted file mode 100644
index 9e3c3e19bea..00000000000
--- a/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval
- of assets
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
new file mode 100644
index 00000000000..9309f961345
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
new file mode 100644
index 00000000000..ec9ceab3d81
--- /dev/null
+++ b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
@@ -0,0 +1,4 @@
+---
+title: Log rescued exceptions to Sentry
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml b/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml
new file mode 100644
index 00000000000..9589659cdc2
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml
@@ -0,0 +1,4 @@
+---
+title: Remove remaining N+1 queries in merge requests API with emojis and labels
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-optimize-project-commit-api.yml b/changelogs/unreleased/sh-optimize-project-commit-api.yml
new file mode 100644
index 00000000000..e6a8a80593c
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-project-commit-api.yml
@@ -0,0 +1,4 @@
+---
+title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
+merge_request:
+author:
diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
new file mode 100644
index 00000000000..6bf03d9a382
--- /dev/null
+++ b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
@@ -0,0 +1,5 @@
+---
+title: Cache open issue and merge request counts for project tabs to speed up project
+ pages
+merge_request: 12457
+author:
diff --git a/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml b/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml
new file mode 100644
index 00000000000..00f55edc2b7
--- /dev/null
+++ b/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml
@@ -0,0 +1,4 @@
+---
+title: Make loading new merge requests (those created after the 9.4 upgrade) faster
+merge_request:
+author:
diff --git a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
new file mode 100644
index 00000000000..7e66ea4ca8b
--- /dev/null
+++ b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure participants for issues, merge requests, etc. are calculated correctly
+ when sending notifications
+merge_request:
+author:
diff --git a/changelogs/unreleased/tc-follow-up-mia.yml b/changelogs/unreleased/tc-follow-up-mia.yml
new file mode 100644
index 00000000000..6327f02032e
--- /dev/null
+++ b/changelogs/unreleased/tc-follow-up-mia.yml
@@ -0,0 +1,4 @@
+---
+title: Undo adding the /reassign quick action
+merge_request: 12701
+author:
diff --git a/changelogs/unreleased/update_bootsnap_1-1-1.yml b/changelogs/unreleased/update_bootsnap_1-1-1.yml
deleted file mode 100644
index 9ecfe4b60c8..00000000000
--- a/changelogs/unreleased/update_bootsnap_1-1-1.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Bump bootsnap to 1.1.1
-merge_request: 12425
-author: @blackst0ne
diff --git a/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml b/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml
new file mode 100644
index 00000000000..571279d3dc7
--- /dev/null
+++ b/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Username and password are no longer stripped from import url on mirror update
+merge_request: 12725
+author:
diff --git a/changelogs/unreleased/workhorse-2-3-0.yml b/changelogs/unreleased/workhorse-2-3-0.yml
new file mode 100644
index 00000000000..17992c8b0ff
--- /dev/null
+++ b/changelogs/unreleased/workhorse-2-3-0.yml
@@ -0,0 +1,4 @@
+---
+title: Upgrade GitLab Workhorse to v2.3.0
+merge_request: 12676
+author:
diff --git a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
new file mode 100644
index 00000000000..0ace7b99657
--- /dev/null
+++ b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
@@ -0,0 +1,4 @@
+---
+title: Split pipelines as internal and external in the usage data
+merge_request: 12277
+author:
diff --git a/config/application.rb b/config/application.rb
index 12242c3b0f5..2f4e2624195 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -26,7 +26,8 @@ module Gitlab
#{config.root}/app/models/members
#{config.root}/app/models/project_services
#{config.root}/app/workers/concerns
- #{config.root}/app/services/concerns))
+ #{config.root}/app/services/concerns
+ #{config.root}/app/finders/concerns))
config.generators.templates.push("#{config.root}/generator_templates")
@@ -105,11 +106,12 @@ module Gitlab
config.assets.precompile << "katex.css"
config.assets.precompile << "katex.js"
config.assets.precompile << "xterm/xterm.css"
- config.assets.precompile << "peek.css"
+ config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css"
config.assets.precompile << "new_nav.css"
+ config.assets.precompile << "new_sidebar.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
@@ -161,5 +163,23 @@ module Gitlab
config.generators do |g|
g.factory_girl false
end
+
+ config.after_initialize do
+ Rails.application.reload_routes!
+
+ project_url_helpers = Module.new do
+ extend ActiveSupport::Concern
+
+ Gitlab::Application.routes.named_routes.helper_names.each do |name|
+ next unless name.include?('namespace_project')
+
+ define_method(name.sub('namespace_project', 'project')) do |project, *args|
+ send(name, project&.namespace, project, *args)
+ end
+ end
+ end
+
+ Gitlab::Routing.add_helpers(project_url_helpers)
+ end
end
end
diff --git a/config/boot.rb b/config/boot.rb
index 02baeab29ab..2d01092acd5 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -5,12 +5,6 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-begin
- require 'bootsnap/setup'
-rescue SystemCallError => exception
- $stderr.puts "WARNING: Bootsnap failed to setup: #{exception.message}"
-end
-
# set default directory for multiproces metrics gathering
if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test'
ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 43a8c0078ca..221e3d6e03b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -450,10 +450,6 @@ production: &base
# Gitaly settings
gitaly:
- # This setting controls whether GitLab uses Gitaly (new component
- # introduced in 9.0). Eventually Gitaly use will become mandatory and
- # this option will disappear.
- enabled: true
# 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.
@@ -543,6 +539,10 @@ production: &base
# enabled: true
# host: localhost
# port: 3808
+ prometheus:
+ # Time between sampling of unicorn socket metrics, in seconds
+ # unicorn_sampler_interval: 10
+
#
# 5. Extra customization
@@ -615,6 +615,52 @@ test:
title: "JIRA"
url: https://sample_company.atlassian.net
project_key: PROJECT
+
+ omniauth:
+ enabled: true
+ allow_single_sign_on: true
+ external_providers: []
+
+ providers:
+ - { name: 'cas3',
+ label: 'cas3',
+ args: { url: 'https://sso.example.com',
+ disable_ssl_verification: false,
+ login_url: '/cas/login',
+ service_validate_url: '/cas/p3/serviceValidate',
+ logout_url: '/cas/logout'} }
+ - { name: 'github',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.com/",
+ verify_ssl: false,
+ args: { scope: 'user:email' } }
+ - { name: 'bitbucket',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'gitlab',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ - { name: 'google_oauth2',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ - { name: 'facebook',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'twitter',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'auth0',
+ args: {
+ client_id: 'YOUR_AUTH0_CLIENT_ID',
+ client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ namespace: 'YOUR_AUTH0_DOMAIN' } }
+ - { name: 'authentiq',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET',
+ args: { scope: 'aq:name email~rs address aq:push' } }
ldap:
enabled: false
servers:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8ddf8e4d2e4..fa33e602e93 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -483,7 +483,6 @@ Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour
# Gitaly
#
Settings['gitaly'] ||= Settingslogic.new({})
-Settings.gitaly['enabled'] = true if Settings.gitaly['enabled'].nil?
#
# Webpack settings
@@ -495,6 +494,12 @@ Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808
#
+# Prometheus metrics settings
+#
+Settings['prometheus'] ||= Settingslogic.new({})
+Settings.prometheus['unicorn_sampler_interval'] ||= 10
+
+#
# Testing settings
#
if Rails.env.test?
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 2bd159ca7f1..482613dacc9 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,6 +1,8 @@
-required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
-current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
+unless Rails.env.test?
+ required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
+ current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
-unless current_version.valid? && required_version <= current_version
- warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
+ unless current_version.valid? && required_version <= current_version
+ warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
+ end
end
diff --git a/config/initializers/8_gitaly.rb b/config/initializers/8_gitaly.rb
index 31c7c91d78f..f4f116e67f7 100644
--- a/config/initializers/8_gitaly.rb
+++ b/config/initializers/8_gitaly.rb
@@ -1,8 +1,6 @@
require 'uri'
-if Gitlab.config.gitaly.enabled || Rails.env.test?
- Gitlab.config.repositories.storages.keys.each do |storage|
- # Force validation of each address
- Gitlab::GitalyClient.address(storage)
- end
+Gitlab.config.repositories.storages.keys.each do |storage|
+ # Force validation of each address
+ Gitlab::GitalyClient.address(storage)
end
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index a0a63ddf8f0..d56fd7a6cfa 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
end
# rubocop:enable Metrics/AbcSize
+Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
+
+Gitlab::Application.configure do |config|
+ # 0 should be Sentry to catch errors in this middleware
+ config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
+end
+
if Gitlab::Metrics.enabled?
require 'pathname'
require 'influxdb'
@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable
- Gitlab::Metrics::Sampler.new.start
+ Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections
def connect(*args)
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index a5636765774..8e2e639fc41 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -87,9 +87,7 @@ Doorkeeper.configure do
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
- # If not specified, Doorkeeper enables all the four grant flows.
- #
- grant_flows %w(authorization_code password client_credentials)
+ grant_flows %w(authorization_code implicit password client_credentials)
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index 4ff9019c43c..c58f425b19b 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -29,7 +29,7 @@ Doorkeeper::OpenidConnect.configure do
o.claim(:email) { |user| user.public_email }
o.claim(:email_verified) { |user| true if user.public_email? }
o.claim(:website) { |user| user.full_website_url if user.website_url? }
- o.claim(:profile) { |user| Rails.application.routes.url_helpers.user_url user }
+ o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user }
o.claim(:picture) { |user| user.avatar_url(only_path: false) }
end
end
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
index 0fee832788d..bfab8c77a4b 100644
--- a/config/initializers/flipper.rb
+++ b/config/initializers/flipper.rb
@@ -1,4 +1,8 @@
require 'flipper/middleware/memoizer'
-Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
- lambda { Feature.flipper }
+unless Rails.env.test?
+ Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
+ lambda { Feature.flipper }
+
+ Feature.register_feature_groups
+end
diff --git a/config/initializers/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb
index 03ac55be0b6..d9d3034150f 100644
--- a/config/initializers/relative_naming_ci_namespace.rb
+++ b/config/initializers/relative_naming_ci_namespace.rb
@@ -4,10 +4,10 @@
# - [project.namespace, project, build]
#
# instead of:
-# - namespace_project_job_path(project.namespace, project, build)
+# - project_job_path(project, build)
#
# Without that, Ci:: namespace is used for resolving routes:
-# - namespace_project_ci_build_path(project.namespace, project, build)
+# - project_ci_build_path(project, build)
module Ci
def self.use_relative_model_naming?
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index daecde49570..d33fae4182d 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -1,32 +1,82 @@
-- group: Kubernetes
- priority: 1
+- group: AWS Elastic Load Balancer
+ priority: 10
metrics:
- - title: "Memory usage"
- y_label: "Values"
+ - title: "Throughput"
+ y_label: "Requests / Sec"
required_metrics:
- - container_memory_usage_bytes
+ - aws_elb_request_count_sum
weight: 1
queries:
- - query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
- label: Container memory
- unit: MiB
- - title: "Current memory usage"
+ - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60'
+ label: Total
+ unit: req / sec
+ - title: "Latency"
+ y_label: "Latency (ms)"
required_metrics:
- - container_memory_usage_bytes
+ - aws_elb_latency_average
weight: 1
queries:
- - query: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
- display_empty: false
- unit: MiB
- - title: "CPU usage"
+ - query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
+ label: Average
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
required_metrics:
- - container_cpu_usage_seconds_total
+ - aws_elb_request_count_sum
+ - aws_elb_httpcode_backend_5_xx_sum
+ weight: 1
+ queries:
+ - query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
+ label: HTTP Errors
+ unit: "%"
+- group: NGINX
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - nginx_requests_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
+ label: Total
+ unit: req / sec
+ - title: "Latency"
+ y_label: "Latency (ms)"
+ required_metrics:
+ - nginx_upstream_response_msecs_avg
+ weight: 1
+ queries:
+ - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000'
+ label: Upstream
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
+ required_metrics:
+ - nginx_responses_total
+ weight: 1
+ queries:
+ - query_range: 'sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}})'
+ label: HTTP Errors
+ unit: "%"
+- group: Kubernetes
+ priority: 5
+ metrics:
+ - title: "Memory Usage"
+ y_label: "Memory Usage (MB)"
+ required_metrics:
+ - container_memory_usage_bytes
weight: 1
queries:
- - query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
- - title: "Current CPU usage"
+ - query_range: '(sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024'
+ label: Average
+ unit: MB
+ - title: "CPU Utilization"
+ y_label: "CPU Utilization (%)"
required_metrics:
- container_cpu_usage_seconds_total
weight: 1
queries:
- - query: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
+ - query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100'
+ label: Average
+ unit: "%"
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 11cdff55ed8..23052a6c6dc 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -12,7 +12,7 @@ scope(path: 'groups/*group_id',
end
resource :avatar, only: [:destroy]
- resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] do
+ resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
@@ -23,6 +23,14 @@ scope(path: 'groups/*group_id',
resources :labels, except: [:show] do
post :toggle_subscription, on: :member
end
+
+ scope path: '-' do
+ namespace :settings do
+ resource :ci_cd, only: [:show], controller: 'ci_cd'
+ end
+
+ resources :variables, only: [:index, :show, :update, :create, :destroy]
+ end
end
scope(path: 'groups/*id',
diff --git a/config/routes/legacy_builds.rb b/config/routes/legacy_builds.rb
new file mode 100644
index 00000000000..5ab2b953ce1
--- /dev/null
+++ b/config/routes/legacy_builds.rb
@@ -0,0 +1,22 @@
+resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
+ collection do
+ resources :artifacts, only: [], controller: 'build_artifacts' do
+ collection do
+ get :latest_succeeded,
+ path: '*ref_name_and_path',
+ format: false
+ end
+ end
+ end
+
+ member do
+ get :raw
+ end
+
+ resource :artifacts, only: [], controller: 'build_artifacts' do
+ get :download
+ get :browse, path: 'browse(/*path)', format: false
+ get :file, path: 'file/*path', format: false
+ get :raw, path: 'raw/*path', format: false
+ end
+end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 19e18c733b1..62cab25c763 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -1,5 +1,4 @@
require 'constraints/project_url_constrainer'
-require 'gitlab/routes/legacy_builds'
resources :projects, only: [:index, :new, :create]
@@ -87,13 +86,8 @@ constraints(ProjectUrlConstrainer.new) do
resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
- resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do
+ resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do
member do
- get :commits
- get :diffs
- get :conflicts
- get :conflict_for_path
- get :pipelines
get :commit_change_content
post :merge
post :cancel_merge_when_pipeline_succeeds
@@ -101,18 +95,32 @@ constraints(ProjectUrlConstrainer.new) do
get :ci_environments_status
post :toggle_subscription
post :remove_wip
- get :diff_for_path
- post :resolve_conflicts
post :assign_related_issues
+
+ scope constraints: { format: nil }, action: :show do
+ get :commits, defaults: { tab: 'commits' }
+ get :pipelines, defaults: { tab: 'pipelines' }
+ get :diffs, defaults: { tab: 'diffs' }
+ end
+
+ scope constraints: { format: 'json' }, as: :json do
+ get :commits
+ get :pipelines
+ get :diffs, to: 'merge_requests/diffs#show'
+ end
+
+ get :diff_for_path, controller: 'merge_requests/diffs'
+
+ scope controller: 'merge_requests/conflicts' do
+ get :conflicts, action: :show
+ get :conflict_for_path
+ post :resolve_conflicts
+ end
end
collection do
- get :branch_from
- get :branch_to
- get :update_branches
get :diff_for_path
post :bulk_update
- get :new_diffs, path: 'new/diffs'
end
resources :discussions, only: [], constraints: { id: /\h{40}/ } do
@@ -123,6 +131,29 @@ constraints(ProjectUrlConstrainer.new) do
end
end
+ controller 'merge_requests/creations', path: 'merge_requests' do
+ post '', action: :create, as: nil
+
+ scope path: 'new', as: :new_merge_request do
+ get '', action: :new
+
+ scope constraints: { format: nil }, action: :new do
+ get :diffs, defaults: { tab: 'diffs' }
+ get :pipelines, defaults: { tab: 'pipelines' }
+ end
+
+ scope constraints: { format: 'json' }, as: :json do
+ get :diffs
+ get :pipelines
+ end
+
+ get :diff_for_path
+ get :update_branches
+ get :branch_from
+ get :branch_to
+ end
+ end
+
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
member do
@@ -221,7 +252,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- Gitlab::Routes::LegacyBuilds.new(self).draw
+ draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
member do
@@ -355,7 +386,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
namespace :settings do
- resource :members, only: [:show]
+ get :members, to: redirect('/%{namespace_id}/%{project_id}/project_members')
resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 2e8c94655c1..c3fdca59a86 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -70,7 +70,8 @@ var config = {
raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
- peek: './peek.js',
+ performance_bar: './performance_bar.js',
+ webpack_runtime: './webpack.js',
},
output: {
@@ -163,6 +164,7 @@ var config = {
'issue_show',
'job_details',
'merge_conflicts',
+ 'monitoring',
'notebook_viewer',
'pdf_viewer',
'pipelines',
@@ -189,7 +191,7 @@ var config = {
// create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({
- names: ['main', 'locale', 'common', 'runtime'],
+ names: ['main', 'locale', 'common', 'webpack_runtime'],
}),
],
@@ -244,7 +246,6 @@ if (IS_DEV_SERVER) {
hot: DEV_SERVER_LIVERELOAD,
inline: DEV_SERVER_LIVERELOAD
};
- config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath;
config.plugins.push(
// watch node_modules for changes if we encounter a missing module compile error
new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
diff --git a/db/migrate/20160804142904_add_ci_config_file_to_project.rb b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
new file mode 100644
index 00000000000..341ae555c1b
--- /dev/null
+++ b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
@@ -0,0 +1,11 @@
+class AddCiConfigFileToProject < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :ci_config_path, :string
+ end
+
+ def down
+ remove_column :projects, :ci_config_path
+ end
+end
diff --git a/db/migrate/20170525130346_create_group_variables_table.rb b/db/migrate/20170525130346_create_group_variables_table.rb
new file mode 100644
index 00000000000..eaa38dbc40d
--- /dev/null
+++ b/db/migrate/20170525130346_create_group_variables_table.rb
@@ -0,0 +1,23 @@
+class CreateGroupVariablesTable < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ create_table :ci_group_variables do |t|
+ t.string :key, null: false
+ t.text :value
+ t.text :encrypted_value
+ t.string :encrypted_value_salt
+ t.string :encrypted_value_iv
+ t.integer :group_id, null: false
+ t.boolean :protected, default: false, null: false
+
+ t.timestamps_with_timezone null: false
+ end
+
+ add_index :ci_group_variables, [:group_id, :key], unique: true
+ end
+
+ def down
+ drop_table :ci_group_variables
+ end
+end
diff --git a/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb b/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb
new file mode 100644
index 00000000000..0146235c5ba
--- /dev/null
+++ b/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb
@@ -0,0 +1,15 @@
+class AddForeignKeyToGroupVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :ci_group_variables, :namespaces, column: :group_id
+ end
+
+ def down
+ remove_foreign_key :ci_group_variables, column: :group_id
+ end
+end
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
new file mode 100644
index 00000000000..3eaafac321d
--- /dev/null
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -0,0 +1,187 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ CONCURRENCY = 4
+
+ disable_ddl_transaction!
+
+ # The tables/columns for which to remove orphans and add foreign keys. Order
+ # matters as some tables/columns should be processed before others.
+ TABLES = [
+ [:boards, :projects, :project_id],
+ [:lists, :labels, :label_id],
+ [:lists, :boards, :board_id],
+ [:services, :projects, :project_id],
+ [:forked_project_links, :projects, :forked_to_project_id],
+ [:merge_requests, :projects, :target_project_id],
+ [:labels, :projects, :project_id],
+ [:issues, :projects, :project_id],
+ [:events, :projects, :project_id],
+ [:milestones, :projects, :project_id],
+ [:notes, :projects, :project_id],
+ [:snippets, :projects, :project_id],
+ [:web_hooks, :projects, :project_id],
+ [:protected_branch_merge_access_levels, :protected_branches, :protected_branch_id],
+ [:protected_branch_push_access_levels, :protected_branches, :protected_branch_id],
+ [:protected_branches, :projects, :project_id],
+ [:protected_tags, :projects, :project_id],
+ [:deploy_keys_projects, :projects, :project_id],
+ [:users_star_projects, :projects, :project_id],
+ [:releases, :projects, :project_id],
+ [:project_group_links, :projects, :project_id],
+ [:pages_domains, :projects, :project_id],
+ [:todos, :projects, :project_id],
+ [:project_import_data, :projects, :project_id],
+ [:project_features, :projects, :project_id],
+ [:ci_builds, :projects, :project_id],
+ [:ci_pipelines, :projects, :project_id],
+ [:ci_runner_projects, :projects, :project_id],
+ [:ci_triggers, :projects, :project_id],
+ [:environments, :projects, :project_id],
+ [:deployments, :projects, :project_id]
+ ]
+
+ def up
+ # These existing foreign keys don't have an "ON DELETE CASCADE" clause.
+ remove_foreign_key_without_error(:boards, :project_id)
+ remove_foreign_key_without_error(:lists, :label_id)
+ remove_foreign_key_without_error(:lists, :board_id)
+ remove_foreign_key_without_error(:protected_branch_merge_access_levels,
+ :protected_branch_id)
+
+ remove_foreign_key_without_error(:protected_branch_push_access_levels,
+ :protected_branch_id)
+
+ remove_orphaned_rows
+ add_foreign_keys
+
+ # These columns are not indexed yet, meaning a cascading delete would take
+ # forever.
+ add_concurrent_index(:project_group_links, :project_id)
+ add_concurrent_index(:pages_domains, :project_id)
+ end
+
+ def down
+ TABLES.each do |(source, _, column)|
+ remove_foreign_key_without_error(source, column)
+ end
+
+ add_concurrent_foreign_key(:boards, :projects, column: :project_id)
+ add_concurrent_foreign_key(:lists, :labels, column: :label_id)
+ add_concurrent_foreign_key(:lists, :boards, column: :board_id)
+
+ add_concurrent_foreign_key(:protected_branch_merge_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+
+ add_concurrent_foreign_key(:protected_branch_push_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+
+ remove_index_without_error(:project_group_links, :project_id)
+ remove_index_without_error(:pages_domains, :project_id)
+ end
+
+ def add_foreign_keys
+ TABLES.each do |(source, target, column)|
+ add_concurrent_foreign_key(source, target, column: column)
+ end
+ end
+
+ # Removes orphans from various tables concurrently.
+ def remove_orphaned_rows
+ Gitlab::Database.with_connection_pool(CONCURRENCY) do |pool|
+ queues = queues_for_rows(TABLES)
+
+ threads = queues.map do |queue|
+ Thread.new do
+ pool.with_connection do |connection|
+ Thread.current[:foreign_key_connection] = connection
+
+ # Disables statement timeouts for the current connection. This is
+ # necessary as removing of orphaned data might otherwise exceed the
+ # statement timeout.
+ disable_statement_timeout
+
+ remove_orphans(*queue.pop) until queue.empty?
+
+ steal_from_queues(queues - [queue])
+ end
+ end
+ end
+
+ threads.each(&:join)
+ end
+ end
+
+ def steal_from_queues(queues)
+ loop do
+ stolen = false
+
+ queues.each do |queue|
+ # Stealing is racy so it's possible a pop might be called on an
+ # already-empty queue.
+ begin
+ remove_orphans(*queue.pop(true))
+ stolen = true
+ rescue ThreadError
+ end
+ end
+
+ break unless stolen
+ end
+ end
+
+ def remove_orphans(source, target, column)
+ quoted_source = quote_table_name(source)
+ quoted_target = quote_table_name(target)
+ quoted_column = quote_column_name(column)
+
+ execute <<-EOF.strip_heredoc
+ DELETE FROM #{quoted_source}
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM #{quoted_target}
+ WHERE #{quoted_target}.id = #{quoted_source}.#{quoted_column}
+ )
+ AND #{quoted_source}.#{quoted_column} IS NOT NULL
+ EOF
+ end
+
+ def remove_foreign_key_without_error(table, column)
+ remove_foreign_key(table, column: column)
+ rescue ArgumentError
+ end
+
+ def remove_index_without_error(table, column)
+ remove_concurrent_index(table, column)
+ rescue ArgumentError
+ end
+
+ def connection
+ # Rails memoizes connection objects, but this causes them to be shared
+ # amongst threads; we don't want that.
+ Thread.current[:foreign_key_connection] || ActiveRecord::Base.connection
+ end
+
+ def queues_for_rows(rows)
+ queues = Array.new(CONCURRENCY) { Queue.new }
+ slice_size = rows.length / CONCURRENCY
+
+ # Divide all the tuples as evenly as possible amongst the queues.
+ rows.each_slice(slice_size).each_with_index do |slice, index|
+ bucket = index % CONCURRENCY
+
+ slice.each do |row|
+ queues[bucket] << row
+ end
+ end
+
+ queues
+ end
+end
diff --git a/db/migrate/20170616133147_create_merge_request_diff_commits.rb b/db/migrate/20170616133147_create_merge_request_diff_commits.rb
new file mode 100644
index 00000000000..616464cb470
--- /dev/null
+++ b/db/migrate/20170616133147_create_merge_request_diff_commits.rb
@@ -0,0 +1,20 @@
+class CreateMergeRequestDiffCommits < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :merge_request_diff_commits, id: false do |t|
+ t.datetime_with_timezone :authored_date
+ t.datetime_with_timezone :committed_date
+ t.belongs_to :merge_request_diff, null: false, foreign_key: { on_delete: :cascade }
+ t.integer :relative_order, null: false
+ t.binary :sha, null: false, limit: 20
+ t.text :author_name
+ t.text :author_email
+ t.text :committer_name
+ t.text :committer_email
+ t.text :message
+
+ t.index [:merge_request_diff_id, :relative_order], name: 'index_merge_request_diff_commits_on_mr_diff_id_and_order', unique: true
+ end
+ end
+end
diff --git a/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb b/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..92833765a82
--- /dev/null
+++ b/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb
@@ -0,0 +1,25 @@
+class CreateCiPipelineScheduleVariables < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ create_table :ci_pipeline_schedule_variables do |t|
+ t.string :key, null: false
+ t.text :value
+ t.text :encrypted_value
+ t.string :encrypted_value_salt
+ t.string :encrypted_value_iv
+ t.integer :pipeline_schedule_id, null: false
+
+ t.timestamps_with_timezone null: true
+ end
+
+ add_index :ci_pipeline_schedule_variables,
+ [:pipeline_schedule_id, :key],
+ name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key",
+ unique: true
+ end
+
+ def down
+ drop_table :ci_pipeline_schedule_variables
+ end
+end
diff --git a/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb b/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..7bbf66e0ac3
--- /dev/null
+++ b/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb
@@ -0,0 +1,15 @@
+class AddForeignKeyToCiPipelineScheduleVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(:ci_pipeline_schedule_variables, :ci_pipeline_schedules, column: :pipeline_schedule_id)
+ end
+
+ def down
+ remove_foreign_key(:ci_pipeline_schedule_variables, column: :pipeline_schedule_id)
+ end
+end
diff --git a/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb
new file mode 100644
index 00000000000..46497775527
--- /dev/null
+++ b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb
@@ -0,0 +1,40 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CorrectProtectedBranchesForeignKeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_foreign_key_without_error(:protected_branch_push_access_levels,
+ column: :protected_branch_id)
+
+ execute <<-EOF
+ DELETE FROM protected_branch_push_access_levels
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM protected_branches
+ WHERE protected_branch_push_access_levels.protected_branch_id = protected_branches.id
+ )
+ AND protected_branch_id IS NOT NULL
+ EOF
+
+ add_concurrent_foreign_key(:protected_branch_push_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+ end
+
+ def down
+ # Previously there was a foreign key without a CASCADING DELETE, so we'll
+ # just leave the foreign key in place.
+ end
+
+ def remove_foreign_key_without_error(*args)
+ remove_foreign_key(*args)
+ rescue ArgumentError
+ end
+end
diff --git a/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb
new file mode 100644
index 00000000000..9f524fac8a7
--- /dev/null
+++ b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb
@@ -0,0 +1,30 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddForeignKeyForMergeRequestDiffs < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ execute <<-EOF
+ DELETE FROM merge_request_diffs
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM merge_requests
+ WHERE merge_requests.id = merge_request_diffs.merge_request_id
+ )
+ EOF
+
+ add_concurrent_foreign_key(:merge_request_diffs,
+ :merge_requests,
+ column: :merge_request_id)
+ end
+
+ def down
+ remove_foreign_key(:merge_request_diffs, column: :merge_request_id)
+ end
+end
diff --git a/db/migrate/20170622135451_rename_duplicated_variable_key.rb b/db/migrate/20170622135451_rename_duplicated_variable_key.rb
new file mode 100644
index 00000000000..368718ab0ce
--- /dev/null
+++ b/db/migrate/20170622135451_rename_duplicated_variable_key.rb
@@ -0,0 +1,38 @@
+class RenameDuplicatedVariableKey < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ execute(<<~SQL)
+ UPDATE ci_variables
+ SET #{key} = CONCAT(#{key}, #{underscore}, id)
+ WHERE id IN (
+ SELECT *
+ FROM ( -- MySQL requires an extra layer
+ SELECT dup.id
+ FROM ci_variables dup
+ INNER JOIN (SELECT max(id) AS id, #{key}, project_id
+ FROM ci_variables tmp
+ GROUP BY #{key}, project_id) var
+ USING (#{key}, project_id) where dup.id <> var.id
+ ) dummy
+ )
+ SQL
+ end
+
+ def down
+ # noop
+ end
+
+ def key
+ # key needs to be quoted in MySQL
+ quote_column_name('key')
+ end
+
+ def underscore
+ quote('_')
+ end
+end
diff --git a/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb
new file mode 100644
index 00000000000..17fe062d8d5
--- /dev/null
+++ b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb
@@ -0,0 +1,15 @@
+class AddEnvironmentScopeToCiVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:ci_variables, :environment_scope, :string, default: '*')
+ end
+
+ def down
+ remove_column(:ci_variables, :environment_scope)
+ end
+end
diff --git a/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb
new file mode 100644
index 00000000000..8b2cc40ee59
--- /dev/null
+++ b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb
@@ -0,0 +1,38 @@
+class AddUniqueConstraintToCiVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless this_index_exists?
+ add_concurrent_index(:ci_variables, columns, name: index_name, unique: true)
+ end
+ end
+
+ def down
+ if this_index_exists?
+ if Gitlab::Database.mysql? && !index_exists?(:ci_variables, :project_id)
+ # Need to add this index for MySQL project_id foreign key constraint
+ add_concurrent_index(:ci_variables, :project_id)
+ end
+
+ remove_concurrent_index(:ci_variables, columns, name: index_name)
+ end
+ end
+
+ private
+
+ def this_index_exists?
+ index_exists?(:ci_variables, columns, name: index_name)
+ end
+
+ def columns
+ @columns ||= [:project_id, :key, :environment_scope]
+ end
+
+ def index_name
+ 'index_ci_variables_on_project_id_and_key_and_environment_scope'
+ end
+end
diff --git a/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb
new file mode 100644
index 00000000000..ddcc0292b9d
--- /dev/null
+++ b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb
@@ -0,0 +1,19 @@
+class RemoveCiVariablesProjectIdIndex < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if index_exists?(:ci_variables, :project_id)
+ remove_concurrent_index(:ci_variables, :project_id)
+ end
+ end
+
+ def down
+ unless index_exists?(:ci_variables, :project_id)
+ add_concurrent_index(:ci_variables, :project_id)
+ end
+ end
+end
diff --git a/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
new file mode 100644
index 00000000000..68b947583d3
--- /dev/null
+++ b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
@@ -0,0 +1,35 @@
+class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless index_exists?(:ci_builds, :stage_id)
+ add_concurrent_index(:ci_builds, :stage_id)
+ end
+
+ unless foreign_key_exists?(:ci_builds, :stage_id)
+ add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
+ end
+ end
+
+ def down
+ if foreign_key_exists?(:ci_builds, :stage_id)
+ remove_foreign_key(:ci_builds, column: :stage_id)
+ end
+
+ if index_exists?(:ci_builds, :stage_id)
+ remove_concurrent_index(:ci_builds, :stage_id)
+ end
+ end
+
+ private
+
+ def foreign_key_exists?(table, column)
+ foreign_keys(:ci_builds).any? do |key|
+ key.options[:column] == column.to_s
+ end
+ end
+end
diff --git a/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb b/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb
new file mode 100644
index 00000000000..fe9970ddc71
--- /dev/null
+++ b/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddPerformanceBarAllowedGroupIdToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :performance_bar_allowed_group_id, :integer
+ end
+end
diff --git a/db/migrate/20170707183807_add_group_id_to_milestones.rb b/db/migrate/20170707183807_add_group_id_to_milestones.rb
new file mode 100644
index 00000000000..675ffd4a1c9
--- /dev/null
+++ b/db/migrate/20170707183807_add_group_id_to_milestones.rb
@@ -0,0 +1,20 @@
+class AddGroupIdToMilestones < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ return if column_exists? :milestones, :group_id
+
+ change_column_null :milestones, :project_id, true
+
+ add_column :milestones, :group_id, :integer
+ end
+
+ def down
+ # We cannot rollback project_id not null constraint if there are records
+ # with null values.
+ execute "DELETE from milestones WHERE project_id IS NULL"
+
+ remove_column :milestones, :group_id
+ change_column :milestones, :project_id, :integer, null: false
+ end
+end
diff --git a/db/migrate/20170707184243_add_group_milestone_id_indexes.rb b/db/migrate/20170707184243_add_group_milestone_id_indexes.rb
new file mode 100644
index 00000000000..aa48fe90cad
--- /dev/null
+++ b/db/migrate/20170707184243_add_group_milestone_id_indexes.rb
@@ -0,0 +1,21 @@
+class AddGroupMilestoneIdIndexes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ return if index_exists?(:milestones, :group_id)
+
+ add_concurrent_foreign_key :milestones, :namespaces, column: :group_id, on_delete: :cascade
+
+ add_concurrent_index :milestones, :group_id
+ end
+
+ def down
+ remove_foreign_key :milestones, column: :group_id
+
+ remove_concurrent_index :milestones, :group_id
+ end
+end
diff --git a/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb b/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb
new file mode 100644
index 00000000000..38536a8b06a
--- /dev/null
+++ b/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb
@@ -0,0 +1,10 @@
+class RemoveWrongVersionsFromSchemaVersions < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ execute("DELETE FROM schema_migrations WHERE version IN ('20170723183807', '20170724184243')")
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
new file mode 100644
index 00000000000..9441b236c8d
--- /dev/null
+++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
@@ -0,0 +1,113 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameAllReservedPathsAgain < ActiveRecord::Migration
+ include Gitlab::Database::RenameReservedPathsMigration::V1
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ TOP_LEVEL_ROUTES = %w[
+ -
+ .well-known
+ abuse_reports
+ admin
+ all
+ api
+ assets
+ autocomplete
+ ci
+ dashboard
+ explore
+ files
+ groups
+ health_check
+ help
+ hooks
+ import
+ invites
+ issues
+ jwt
+ koding
+ member
+ merge_requests
+ new
+ notes
+ notification_settings
+ oauth
+ profile
+ projects
+ public
+ repository
+ robots.txt
+ s
+ search
+ sent_notifications
+ services
+ snippets
+ teams
+ u
+ unicorn_test
+ unsubscribes
+ uploads
+ users
+ ].freeze
+
+ PROJECT_WILDCARD_ROUTES = %w[
+ badges
+ blame
+ blob
+ builds
+ commits
+ create
+ create_dir
+ edit
+ environments/folders
+ files
+ find_file
+ gitlab-lfs/objects
+ info/lfs/objects
+ new
+ preview
+ raw
+ refs
+ tree
+ update
+ wikis
+ ].freeze
+
+ GROUP_ROUTES = %w[
+ activity
+ analytics
+ audit_events
+ avatar
+ edit
+ group_members
+ hooks
+ issues
+ labels
+ ldap
+ ldap_group_links
+ merge_requests
+ milestones
+ notification_setting
+ pipeline_quota
+ projects
+ subgroups
+ ].freeze
+
+ def up
+ disable_statement_timeout
+
+ TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) }
+ PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) }
+ GROUP_ROUTES.each { |route| rename_child_paths(route) }
+ end
+
+ def down
+ disable_statement_timeout
+
+ revert_renames
+ end
+end
diff --git a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
index 7d6609b18bf..ac61b5c84a8 100644
--- a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
+++ b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
@@ -3,19 +3,15 @@ class AddStageIdIndexToBuilds < ActiveRecord::Migration
DOWNTIME = false
- disable_ddl_transaction!
+ ##
+ # Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb
+ #
def up
- unless index_exists?(:ci_builds, :stage_id)
- add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
- add_concurrent_index(:ci_builds, :stage_id)
- end
+ # noop
end
def down
- if index_exists?(:ci_builds, :stage_id)
- remove_foreign_key(:ci_builds, column: :stage_id)
- remove_concurrent_index(:ci_builds, :stage_id)
- end
+ # noop
end
end
diff --git a/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb b/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb
new file mode 100644
index 00000000000..f31015d77a3
--- /dev/null
+++ b/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb
@@ -0,0 +1,33 @@
+class MigrateStageIdReferenceInBackground < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10000
+ RANGE_SIZE = 1000
+ MIGRATION = 'MigrateBuildStageIdReference'.freeze
+
+ disable_ddl_transaction!
+
+ class Build < ActiveRecord::Base
+ self.table_name = 'ci_builds'
+ include ::EachBatch
+ end
+
+ ##
+ # It will take around 3 days to process 20M ci_builds.
+ #
+ def up
+ Build.where(stage_id: nil).each_batch(of: BATCH_SIZE) do |relation, index|
+ relation.each_batch(of: RANGE_SIZE) do |relation|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker
+ .perform_in(index * 2.minutes, MIGRATION, range)
+ end
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8c7440ee610..3dbe52c9c80 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: 20170622162730) do
+ActiveRecord::Schema.define(version: 20170707184244) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -126,6 +126,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do
t.boolean "prometheus_metrics_enabled", default: false, null: false
t.boolean "help_page_hide_commercial_content", default: false
t.string "help_page_support_url"
+ t.integer "performance_bar_allowed_group_id"
end
create_table "audit_events", force: :cascade do |t|
@@ -253,6 +254,33 @@ ActiveRecord::Schema.define(version: 20170622162730) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
+ create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
+ t.string "key", null: false
+ t.text "value"
+ t.text "encrypted_value"
+ t.string "encrypted_value_salt"
+ t.string "encrypted_value_iv"
+ t.integer "pipeline_schedule_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
+
+ create_table "ci_group_variables", force: :cascade do |t|
+ t.string "key", null: false
+ t.text "value"
+ t.text "encrypted_value"
+ t.string "encrypted_value_salt"
+ t.string "encrypted_value_iv"
+ t.integer "group_id", null: false
+ t.boolean "protected", default: false, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
+
create_table "ci_pipeline_schedules", force: :cascade do |t|
t.string "description"
t.string "ref"
@@ -374,9 +402,10 @@ ActiveRecord::Schema.define(version: 20170622162730) do
t.string "encrypted_value_iv"
t.integer "project_id", null: false
t.boolean "protected", default: false, null: false
+ t.string "environment_scope", default: "*", null: false
end
- add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree
+ add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
@@ -692,6 +721,21 @@ ActiveRecord::Schema.define(version: 20170622162730) do
add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
+ create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
+ t.datetime "authored_date"
+ t.datetime "committed_date"
+ t.integer "merge_request_diff_id", null: false
+ t.integer "relative_order", null: false
+ t.binary "sha", null: false
+ t.text "author_name"
+ t.text "author_email"
+ t.text "committer_name"
+ t.text "committer_email"
+ t.text "message"
+ end
+
+ add_index "merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true, using: :btree
+
create_table "merge_request_diff_files", id: false, force: :cascade do |t|
t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false
@@ -799,7 +843,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do
create_table "milestones", force: :cascade do |t|
t.string "title", null: false
- t.integer "project_id", null: false
+ t.integer "project_id"
t.text "description"
t.date "due_date"
t.datetime "created_at"
@@ -810,10 +854,12 @@ ActiveRecord::Schema.define(version: 20170622162730) do
t.text "description_html"
t.date "start_date"
t.integer "cached_markdown_version"
+ t.integer "group_id"
end
add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
+ add_index "milestones", ["group_id"], name: "index_milestones_on_group_id", using: :btree
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree
add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
@@ -970,6 +1016,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do
end
add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
+ add_index "pages_domains", ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
create_table "personal_access_tokens", force: :cascade do |t|
t.integer "user_id", null: false
@@ -1019,6 +1066,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do
end
add_index "project_group_links", ["group_id"], name: "index_project_group_links_on_group_id", using: :btree
+ add_index "project_group_links", ["project_id"], name: "index_project_group_links_on_project_id", using: :btree
create_table "project_import_data", force: :cascade do |t|
t.integer "project_id"
@@ -1085,6 +1133,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do
t.string "import_jid"
t.integer "cached_markdown_version"
t.datetime "last_repository_updated_at"
+ t.string "ci_config_path"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1526,48 +1575,79 @@ ActiveRecord::Schema.define(version: 20170622162730) do
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
- add_foreign_key "boards", "projects"
+ add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
+ add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
+ add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
+ add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
+ add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
+ add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade
add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade
add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade
add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade
+ add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
+ add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
+ add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
+ add_foreign_key "events", "projects", name: "fk_0434b48643", on_delete: :cascade
+ add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
+ add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
- add_foreign_key "lists", "boards"
- add_foreign_key "lists", "labels"
+ add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
+ add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
+ add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
+ add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
+ add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
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", "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
+ add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade
+ add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade
+ add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
+ add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
+ add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
+ add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
- add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
- add_foreign_key "protected_branch_push_access_levels", "protected_branches"
+ add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
+ add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
+ add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade
add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id"
add_foreign_key "protected_tag_create_access_levels", "protected_tags"
add_foreign_key "protected_tag_create_access_levels", "users"
+ add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
+ add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
+ add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
+ add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
+ add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
+ add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
+ add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
end
diff --git a/doc/README.md b/doc/README.md
index ab8ea192a26..9b81c409570 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,12 +1,24 @@
-# GitLab Community Edition
+# GitLab Documentation
-[GitLab](https://about.gitlab.com/) is a Git-based fully featured platform
-for software development.
+Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
+platform for software development!
-**GitLab Community Edition (CE)** is an opensource product, self-hosted, free to use.
-All [GitLab products](https://about.gitlab.com/products/) contain the features
-available in GitLab CE. Premium features are available in
-[GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/).
+We offer four different products for you and your company:
+
+- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
+self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
+- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/),
+self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**.
+- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
+
+**GitLab EE** contains all features available in **GitLab CE**,
+plus premium features available in each version: **Enterprise Edition Starter**
+(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in
+**EES** is also available in **EEP**.
+
+**Note:** _We are unifying the documentation for CE and EE. To check if certain feature is
+available in CE or EE, look for a note right below the page title containing the GitLab
+version which introduced that feature._
----
@@ -125,7 +137,7 @@ have access to GitLab administration tools and settings.
- [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab
- [Authentication/Authorization](topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers.
-### GitLab admins' superpowers
+### Features
- [Container Registry](administration/container_registry.md): Configure Docker Registry with GitLab.
- [Custom Git hooks](administration/custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough.
@@ -155,6 +167,7 @@ have access to GitLab administration tools and settings.
- [Operations](administration/operations.md): Keeping GitLab up and running.
- [Polling](administration/polling.md): Configure how often the GitLab UI polls for updates.
- [Request Profiling](administration/monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
+- [Performance Bar](administration/monitoring/performance/performance_bar.md): Get performance information for the current page.
### Customization
@@ -167,6 +180,7 @@ have access to GitLab administration tools and settings.
### Admin tools
+- [Gitaly](administration/gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service
- [Raketasks](raketasks/README.md): Backups, maintenance, automatic webhook setup and the importing of projects.
- [Backup and restore](raketasks/backup_restore.md): Backup and restore your GitLab instance.
- [Reply by email](administration/reply_by_email.md): Allow users to comment on issues and merge requests by replying to notification emails.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 332457cb384..5732b6a1ca4 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -2,8 +2,7 @@
[Gitaly](https://gitlab.com/gitlab-org/gitaly) (introduced in GitLab
9.0) is a service that provides high-level RPC access to Git
-repositories. As of GitLab 9.3 it is still an optional component with
-limited scope.
+repositories. Gitaly is a mandatory component in GitLab 9.4 and newer.
GitLab components that access Git repositories (gitlab-rails,
gitlab-shell, gitlab-workhorse) act as clients to Gitaly. End users do
@@ -149,6 +148,8 @@ git_data_dirs({
{ 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
{ 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
})
+
+gitlab_rails['gitaly_token'] = 'abc123secret'
```
Source installations:
@@ -164,6 +165,9 @@ gitlab:
storage1:
path: /mnt/gitlab/storage1/repositories
gitaly_address: tcp://gitlab.internal:9999
+
+ gitaly:
+ token: 'abc123secret'
```
Now reconfigure (Omnibus) or restart (source). When you tail the
@@ -172,36 +176,11 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
coming in. One sure way to trigger a Gitaly request is to clone a
repository from your GitLab server over HTTP.
-## Configuring GitLab to not use Gitaly
-
-Gitaly is still an optional component in GitLab 9.3. This means you
-can choose to not use it.
-
-In Omnibus you can make the following change in
-`/etc/gitlab/gitlab.rb` and reconfigure. This will both disable the
-Gitaly service and configure the rest of GitLab not to use it.
-
-```ruby
-gitaly['enable'] = false
-```
-
-In source installations, edit `/home/git/gitlab/config/gitlab.yml` and
-make sure `enabled` in the `gitaly` section is set to 'false'. This
-does not disable the Gitaly service in your init script; it only
-prevents it from being used.
-
-Apply the change with `service gitlab restart`.
-
-```yaml
- gitaly:
- enabled: false
-```
-
## Disabling or enabling the Gitaly service
-Be careful: if you disable Gitaly without instructing the rest of your
-GitLab installation not to use Gitaly, you may end up with errors
-because GitLab tries to access a service that is not running.
+If you are running Gitaly [as a remote
+service](#running-gitaly-on-its-own-server) you may want to disable
+the local Gitaly service that runs on your Gitlab server by default.
To disable the Gitaly service in your Omnibus installation, add the
following line to `/etc/gitlab/gitlab.rb`:
@@ -220,4 +199,4 @@ following to `/etc/default/gitlab`:
gitaly_enabled=false
```
-When you run `service gitlab restart` Gitaly will be disabled. \ No newline at end of file
+When you run `service gitlab restart` Gitaly will be disabled.
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
new file mode 100644
index 00000000000..d38293d2ed6
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png b/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
new file mode 100644
index 00000000000..2d64ef8c5fc
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
new file mode 100644
index 00000000000..7868e2c46d1
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
new file mode 100644
index 00000000000..372ae021f6b
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
new file mode 100644
index 00000000000..ee680c7b258
--- /dev/null
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -0,0 +1,35 @@
+# Performance Bar
+
+A Performance Bar can be displayed, to dig into the performance of a page. When
+activated, it looks as follows:
+
+![Performance Bar](img/performance_bar.png)
+
+It allows you to:
+
+- see the current host serving the page
+- see the timing of the page (backend, frontend)
+- the number of DB queries, the time it took, and the detail of these queries
+![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
+- the number of calls to Redis, and the time it took
+- the number of background jobs created by Sidekiq, and the time it took
+- the number of Ruby GC calls, and the time it took
+- profile the code used to generate the page, line by line
+![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
+
+## Enable the Performance Bar via the Admin panel
+
+GitLab Performance Bar is disabled by default. To enable it for a given group,
+navigate to the Admin area in **Settings > Profiling - Performance Bar**
+(`/admin/application_settings`).
+
+The only required setting you need to set is the full path of the group that
+will be allowed to display the Performance Bar.
+Make sure _Enable the Performance Bar_ is checked and hit
+**Save** to save the changes.
+
+---
+
+![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
+
+---
diff --git a/doc/api/README.md b/doc/api/README.md
index b7f6ee69193..95e7a457848 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -17,6 +17,7 @@ following locations:
- [Deploy Keys](deploy_keys.md)
- [Environments](environments.md)
- [Events](events.md)
+- [Feature flags](features.md)
- [Gitignores templates](templates/gitignores.md)
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Groups](groups.md)
diff --git a/doc/api/features.md b/doc/api/features.md
index 89b8d3ac948..6861dbf00a2 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -1,4 +1,4 @@
-# Features API
+# Features flags API
All methods require administrator authorization.
@@ -58,6 +58,11 @@ POST /features/:name
| --------- | ---- | -------- | ----------- |
| `name` | string | yes | Name of the feature to create or update |
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
+| `feature_group` | string | no | A Feature group name |
+| `user` | string | no | A GitLab username |
+
+Note that you can enable or disable a feature for both a `feature_group` and a
+`user` with a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/issues.md b/doc/api/issues.md
index df5666bb7b6..a00a63bad4b 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -964,3 +964,30 @@ Example response:
## Comments on issues
Comments are done via the [notes](notes.md) resource.
+
+## Get user agent details
+
+Available only for admins.
+
+```
+GET /projects/:id/issues/:issue_iid/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 4ad6071a0ed..8133251dffe 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -29,22 +29,30 @@ Example response:
{
"id": 1,
"path": "user1",
- "kind": "user"
+ "kind": "user",
+ "full_path": "user1"
},
{
"id": 2,
"path": "group1",
- "kind": "group"
+ "kind": "group",
+ "full_path": "group1",
+ "parent_id": "null",
+ "members_count_with_descendants": 2
},
{
"id": 3,
"path": "bar",
"kind": "group",
"full_path": "foo/bar",
+ "parent_id": "9",
+ "members_count_with_descendants": 5
}
]
```
+**Note**: `members_count_with_descendants` are presented only for group masters/owners.
+
## Search for namespace
Get all namespaces that match a string in their name or path.
@@ -72,6 +80,8 @@ Example response:
"path": "twitter",
"kind": "group",
"full_path": "twitter",
+ "parent_id": "null",
+ "members_count_with_descendants": 2
}
]
```
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 07cb64cb373..77bb7a55d8c 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,59 +1,55 @@
# GitLab as an OAuth2 provider
-This document covers using the OAuth2 protocol to access GitLab.
+This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access Gitlab resources on user's behalf.
-If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
+If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [OAuth2 provider](../integration/oauth_provider.md)
+documentation.
-OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party.
+This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
-This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
+## Supported OAuth2 Flows
-## Web Application Flow
+Gitlab currently supports following authorization flows:
-This is the most common type of flow and is used by server-side clients that wish to access GitLab on a user's behalf.
+* *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
+* *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
+* *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
->**Note:**
-This flow **should not** be used for client-side clients as you would leak your `client_secret`. Client-side clients should use the Implicit Grant (which is currently unsupported).
+Please refer to [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out in details how all those flows work and pick the right one for your use case.
-For more detailed information, check out the [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1)
+Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
+in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
+you'll obtain `application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
-In the following sections you will be introduced to the three steps needed for this flow.
+>**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
+value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
+have been a requirement in the standard!
-### 1. Registering the client
+In the following sections you will find detailed instructions on how to obtain authorization with each flow.
-First, you should create an application (`/profile/applications`) in your user's account.
-Each application gets a unique App ID and App Secret parameters.
+### Web Application Flow
->**Note:**
-**You should not share/leak your App ID or App Secret.**
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1) for a detailed flow description
-### 2. Requesting authorization
+#### 1. Requesting authorization code
-To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint:
+To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint with following GET parameters:
```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=your_unique_state_hash
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
```
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided.
+This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect will
+include the GET `code` parameter, for example:
-The redirect will include the GET `code` parameter, for example:
-
-```
-http://myapp.com/oauth/redirect?code=1234567890&state=your_unique_state_hash
-```
+`http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH`
You should then use the `code` to request an access token.
->**Important:**
-It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and
-validate that value is returned and matches in the redirect request.
-This is important to prevent [CSRF attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow),
-`state` really should have been a requirement in the standard!
-
-### 3. Requesting the access token
+#### 2. Requesting access token
-Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example, we are using Ruby's `rest-client`:
+Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
+we are using Ruby's `rest-client`:
```
parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
@@ -72,28 +68,40 @@ The `redirect_uri` must match the `redirect_uri` used in the original authorizat
You can now make requests to the API with the access token returned.
-### Use the access token to access the API
-The access token allows you to make requests to the API on a behalf of a user.
+### Implicit Grant
+
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.2) for a detailed flow description.
+
+Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
+or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+
+>**Important:** Avoid using this flow for applications that store data outside of the Gitlab instance. If you do, make sure to verify `application id`
+associated with access token before granting access to the data
+(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
+
+
+#### 1. Requesting access token
+
+To request the access token, you should redirect the user to the `/oauth/authorize` endpoint using `token` response type:
```
-GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
```
-Or you can put the token to the Authorization header:
+This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
+will include a fragment with `access_token` as well as token details in GET parameters, for example:
```
-curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
+http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE_HASH&token_type=bearer&expires_in=3600
```
-## Resource Owner Password Credentials
+### Resource Owner Password Credentials
-## Deprecation Notice
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.3) for a detailed flow description.
-1. Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication turned on.
-2. These users can access the API using [personal access tokens] instead.
-
----
+> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
+turned on. These users can access the API using [personal access tokens] instead.
In this flow, a token is requested in exchange for the resource owner credentials (username and password).
The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
@@ -101,12 +109,16 @@ client is part of the device operating system or a highly privileged application
available (such as an authorization code).
>**Important:**
-Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens] are a better choice.
+Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
+are a better choice.
Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
-You can do POST request to `/oauth/token` with parameters:
+
+#### 1. Requesting access token
+
+POST request to `/oauth/token` with parameters:
```
{
@@ -134,4 +146,18 @@ access_token = client.password.get_token('user@example.com', 'secret')
puts access_token.token
```
+## Access Gitlab API with `access token`
+
+The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
+```
+GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
+```
+
+or you can put the token to the Authorization header:
+
+```
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
+```
+
[personal access tokens]: ../user/profile/personal_access_tokens.md
+[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow \ No newline at end of file
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index 92491de4daa..d74398c6e65 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -119,3 +119,35 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
+
+## Get user agent details
+
+> **Notes:**
+> [Introduced][ce-29508] in GitLab 9.4.
+
+
+Available only for admins.
+
+```
+GET /projects/:id/snippets/:snippet_id/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a snippet |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/1/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
+
+[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
diff --git a/doc/api/projects.md b/doc/api/projects.md
index cc1bb3911c8..0d892c74d00 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -173,6 +173,164 @@ Parameters:
]
```
+### List a user's projects
+
+Get a list of visible projects for the given user. When accessed without authentication, only public projects are returned.
+
+```
+GET /users/:user_id/projects
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | string | yes | The ID or username of the user |
+| `archived` | boolean | no | Limit by archived status |
+| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
+| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Return list of projects matching the search criteria |
+| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+| `owned` | boolean | no | Limit by projects owned by the current user |
+| `membership` | boolean | no | Limit by projects that the current user is a member of |
+| `starred` | boolean | no | Limit by projects starred by the current user |
+| `statistics` | boolean | no | Include project statistics |
+| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
+| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
+
+```json
+[
+ {
+ "id": 4,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
+ "web_url": "http://example.com/diaspora/diaspora-client",
+ "tag_list": [
+ "example",
+ "disapora client"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Diaspora Client",
+ "name_with_namespace": "Diaspora / Diaspora Client",
+ "path": "diaspora-client",
+ "path_with_namespace": "diaspora/diaspora-client",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 3,
+ "name": "Diaspora",
+ "path": "diaspora",
+ "kind": "group",
+ "full_path": "diaspora"
+ },
+ "import_status": "none",
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "statistics": {
+ "commit_count": 37,
+ "storage_size": 1038090,
+ "repository_size": 1038090,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ }
+ },
+ {
+ "id": 6,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
+ "http_url_to_repo": "http://example.com/brightbox/puppet.git",
+ "web_url": "http://example.com/brightbox/puppet",
+ "tag_list": [
+ "example",
+ "puppet"
+ ],
+ "owner": {
+ "id": 4,
+ "name": "Brightbox",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Puppet",
+ "name_with_namespace": "Brightbox / Puppet",
+ "path": "puppet",
+ "path_with_namespace": "brightbox/puppet",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 4,
+ "name": "Brightbox",
+ "path": "brightbox",
+ "kind": "group",
+ "full_path": "brightbox"
+ },
+ "import_status": "none",
+ "import_error": null,
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false,
+ "avatar_url": null,
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "statistics": {
+ "commit_count": 12,
+ "storage_size": 2066080,
+ "repository_size": 2066080,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ }
+ }
+]
+```
+
### Get single project
Get a specific project. This endpoint can be accessed without authentication if
@@ -336,7 +494,7 @@ Parameters:
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `visibility` | String | no | See [project visibility level](#project-visibility-level) |
+| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
@@ -346,6 +504,7 @@ Parameters:
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
### Create project for user
@@ -382,6 +541,7 @@ Parameters:
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
### Edit project
@@ -416,6 +576,7 @@ Parameters:
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
+| `ci_config_path` | string | no | The path to CI config file |
### Fork project
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 18ceb8f779e..1fc577561a0 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -61,7 +61,7 @@ POST /projects/:id/repository/files/:file_path
```
```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
```
Example response:
@@ -90,7 +90,7 @@ PUT /projects/:id/repository/files/:file_path
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
```
Example response:
@@ -129,7 +129,7 @@ DELETE /projects/:id/repository/files/:file_path
```
```bash
-curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
```
Example response:
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index efaab712367..fdafbfb5b9e 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -234,3 +234,35 @@ Example response:
}
]
```
+
+## Get user agent details
+
+> **Notes:**
+> [Introduced][ce-29508] in GitLab 9.4.
+
+
+Available only for admins.
+
+```
+GET /snippets/:id/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a snippet |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
+
+[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
diff --git a/doc/api/users.md b/doc/api/users.md
index cf09b8f44aa..91170e79645 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -146,6 +146,12 @@ GET /users?extern_uid=1234567&provider=github
You can search for users who are external with: `/users?external=true`
+You can search users by creation date time range with:
+
+```
+GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
+```
+
## Single user
Get a single user.
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index be4dea55c20..d3433594eb7 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,4 +1,4 @@
-# Using Docker Images
+# Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
[Docker Engine](https://www.docker.com/) to test and build any application.
@@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all
the commands that we will explore later from your shell, rather than having to
test them on a dedicated CI server.
-## Register docker runner
+## Register Docker Runner
-To use GitLab Runner with docker you need to register a new runner to use the
-`docker` executor:
+To use GitLab Runner with Docker you need to [register a new Runner][register]
+to use the `docker` executor.
+
+A one-line example can be seen below:
```bash
-gitlab-ci-multi-runner register \
- --url "https://gitlab.com/" \
+sudo gitlab-runner register \
+ --url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "docker-ruby-2.1" \
--executor "docker" \
@@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \
--docker-mysql latest
```
-The registered runner will use the `ruby:2.1` docker image and will run two
+The registered runner will use the `ruby:2.1` Docker image and will run two
services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process.
## What is an image
-The `image` keyword is the name of the docker image the docker executor
-will run to perform the CI tasks.
+The `image` keyword is the name of the Docker image the Docker executor
+will run to perform the CI tasks.
-By default the executor will only pull images from [Docker Hub][hub],
+By default, the executor will only pull images from [Docker Hub][hub],
but this can be configured in the `gitlab-runner/config.toml` by setting
-the [docker pull policy][] to allow using local images.
+the [Docker pull policy][] to allow using local images.
For more information about images and Docker Hub please read
the [Docker Fundamentals][] documentation.
## What is a service
-The `services` keyword defines just another docker image that is run during
-your job and is linked to the docker image that the `image` keyword defines.
+The `services` keyword defines just another Docker image that is run during
+your job and is linked to the Docker image that the `image` keyword defines.
This allows you to access the service image during build time.
The service image can run any application, but the most common use case is to
@@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an
existing image and run it as an additional container than install `mysql` every
time the project is built.
+You are not limited to have only database services. You can add as many
+services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
+Any image found at [Docker Hub][hub] or your private Container Registry can be
+used as a service.
+
You can see some widely used services examples in the relevant documentation of
[CI services examples](../services/README.md).
@@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container.
The service container for MySQL will be accessible under the hostname `mysql`.
So, in order to access your database service you have to connect to the host
-named `mysql` instead of a socket or `localhost`.
+named `mysql` instead of a socket or `localhost`. Read more in [accessing the
+services](#accessing-the-services).
-## Overwrite image and services
+### Accessing the services
-See [How to use other images as services](#how-to-use-other-images-as-services).
+Let's say that you need a Wordpress instance to test some API integration with
+your application.
-## How to use other images as services
+You can then use for example the [tutum/wordpress][] image in your
+`.gitlab-ci.yml`:
-You are not limited to have only database services. You can add as many
-services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
-Any image found at [Docker Hub][hub] can be used as a service.
+```yaml
+services:
+- tutum/wordpress:latest
+```
+
+If you don't [specify a service alias](#available-settings-for-services-entry),
+when the job is run, `tutum/wordpress` will be started and you will have
+access to it from your build container under two hostnames to choose from:
-## Define image and services from `.gitlab-ci.yml`
+- `tutum-wordpress`
+- `tutum__wordpress`
+
+>**Note:**
+Hostnames with underscores are not RFC valid and may cause problems in 3rd party
+applications.
+
+The default aliases for the service's hostname are created from its image name
+following these rules:
+
+- Everything after the colon (`:`) is stripped
+- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
+ is created
+- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
+ created (requires GitLab Runner v1.1.0 or higher)
+
+To override the default behavior, you can
+[specify a service alias](#available-settings-for-services-entry).
+
+## Define `image` and `services` from `.gitlab-ci.yml`
You can simply define an image that will be used for all jobs and a list of
-services that you want to use during build time.
+services that you want to use during build time:
```yaml
image: ruby:2.2
@@ -125,6 +159,203 @@ test:2.2:
- bundle exec rake spec
```
+Or you can pass some [extended configuration options](#extended-docker-configuration-options)
+for `image` and `services`:
+
+```yaml
+image:
+ name: ruby:2.2
+ entrypoint: ["/bin/bash"]
+
+services:
+- name: my-postgres:9.4
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
+
+before_script:
+- bundle install
+
+test:
+ script:
+ - bundle exec rake spec
+```
+
+## Extended Docker configuration options
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+When configuring the `image` or `services` entries, you can use a string or a map as
+options:
+
+- when using a string as an option, it must be the full name of the image to use
+ (including the Registry part if you want to download the image from a Registry
+ other than Docker Hub)
+- when using a map as an option, then it must contain at least the `name`
+ option, which is the same name of the image as used for the string setting
+
+For example, the following two definitions are equal:
+
+1. Using a string as an option to `image` and `services`:
+
+ ```yaml
+ image: "registry.example.com/my/image:latest"
+
+ services:
+ - postgresql:9.4
+ - redis:latest
+ ```
+
+1. Using a map as an option to `image` and `services`. The use of `image:name` is
+ required:
+
+ ```yaml
+ image:
+ name: "registry.example.com/my/image:latest"
+
+ services:
+ - name: postgresql:9.4
+ - name: redis:latest
+ ```
+
+### Available settings for `image`
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+| Setting | Required | Description |
+|------------|----------|-------------|
+| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
+| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
+
+### Available settings for `services`
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+| Setting | Required | Description |
+|------------|----------|-------------|
+| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
+| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
+| `command` | no | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
+| `alias` | no | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
+
+### Starting multiple services from the same image
+
+Before the new extended Docker configuration options, the following configuration
+would not work properly:
+
+```yaml
+services:
+- mysql:latest
+- mysql:latest
+```
+
+The Runner would start two containers using the `mysql:latest` image, but both
+of them would be added to the job's container with the `mysql` alias based on
+the [default hostname naming](#accessing-the-services). This would end with one
+of the services not being accessible.
+
+After the new extended Docker configuration options, the above example would
+look like:
+
+```yaml
+services:
+- name: mysql:latest
+ alias: mysql-1
+- name: mysql:latest
+ alias: mysql-2
+```
+
+The Runner will still start two containers using the `mysql:latest` image,
+but now each of them will also be accessible with the alias configured
+in `.gitlab-ci.yml` file.
+
+### Setting a command for the service
+
+Let's assume you have a `super/sql:latest` image with some SQL database
+inside it and you would like to use it as a service for your job. Let's also
+assume that this image doesn't start the database process while starting
+the container and the user needs to manually use `/usr/bin/super-sql run` as
+a command to start the database.
+
+Before the new extended Docker configuration options, you would need to create
+your own image based on the `super/sql:latest` image, add the default command,
+and then use it in job's configuration, like:
+
+```Dockerfile
+# my-super-sql:latest image's Dockerfile
+
+FROM super/sql:latest
+CMD ["/usr/bin/super-sql", "run"]
+```
+
+```yaml
+# .gitlab-ci.yml
+
+services:
+- my-super-sql:latest
+```
+
+After the new extended Docker configuration options, you can now simply
+set a `command` in `.gitlab-ci.yml`, like:
+
+```yaml
+# .gitlab-ci.yml
+
+services:
+- name: super/sql:latest
+ command: ["/usr/bin/super-sql", "run"]
+```
+
+As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
+
+### Overriding the entrypoint of an image
+
+Let's assume you have a `super/sql:experimental` image with some SQL database
+inside it and you would like to use it as a base image for your job because you
+want to execute some tests with this database binary. Let's also assume that
+this image is configured with `/usr/bin/super-sql run` as an entrypoint. That
+means, that when starting the container without additional options, it will run
+the database's process, while Runner expects that the image will have no
+entrypoint or at least will start with a shell as its entrypoint.
+
+Previously we would need to create our own image based on the
+`super/sql:experimental` image, set the entrypoint to a shell, and then use
+it in job's configuration, e.g.:
+
+Before the new extended Docker configuration options, you would need to create
+your own image based on the `super/sql:experimental` image, set the entrypoint
+to a shell and then use it in job's configuration, like:
+
+```Dockerfile
+# my-super-sql:experimental image's Dockerfile
+
+FROM super/sql:experimental
+ENTRYPOINT ["/bin/sh"]
+```
+
+```yaml
+# .gitlab-ci.yml
+
+image: my-super-sql:experimental
+```
+
+After the new extended Docker configuration options, you can now simply
+set an `entrypoint` in `.gitlab-ci.yml`, like:
+
+```yaml
+# .gitlab-ci.yml
+
+image:
+ name: super/sql:experimental
+ entrypoint: ["/bin/sh"]
+```
+
+As you can see the syntax of `entrypoint` is similar to
+[Dockerfile's `ENTRYPOINT`][entrypoint].
+
## Define image and services in `config.toml`
Look for the `[runners.docker]` section:
@@ -138,7 +369,7 @@ Look for the `[runners.docker]` section:
The image and services defined this way will be added to all job run by
that runner.
-## Define an image from a private Docker registry
+## Define an image from a private Container Registry
> **Notes:**
- This feature requires GitLab Runner **1.8** or higher
@@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps:
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
-## Accessing the services
-
-Let's say that you need a Wordpress instance to test some API integration with
-your application.
-
-You can then use for example the [tutum/wordpress][] image in your
-`.gitlab-ci.yml`:
-
-```yaml
-services:
-- tutum/wordpress:latest
-```
-
-When the job is run, `tutum/wordpress` will be started and you will have
-access to it from your build container under the hostnames `tutum-wordpress`
-(requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`.
-
-When using a private registry, the image name also includes a hostname and port
-of the registry.
-
-```yaml
-services:
-- docker.example.com:5000/wordpress:latest
-```
-
-The service hostname will also include the registry hostname. Service will be
-available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer)
-and `docker.example.com__wordpress`.
-
-*Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.*
-
-The alias hostnames for the service are made from the image name following these
-rules:
-
-1. Everything after `:` is stripped
-2. Slash (`/`) is replaced with double underscores (`__`) - primary alias
-3. Slash (`/`) is replaced with dash (`-`) - secondary alias, requires GitLab Runner v1.1.0 or newer
-
## Configuring services
Many services accept environment variables which allow you to easily change
@@ -257,7 +450,7 @@ See the specific documentation for
## How Docker integration works
-Below is a high level overview of the steps performed by docker during job
+Below is a high level overview of the steps performed by Docker during job
time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
@@ -274,7 +467,7 @@ time.
## How to debug a job locally
*Note: The following commands are run without root privileges. You should be
-able to run docker with your regular user account.*
+able to run Docker with your regular user account.*
First start with creating a file named `build_script`:
@@ -334,3 +527,6 @@ creation.
[mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
[secret variable]: ../variables/README.md#secret-variables
+[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
+[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
+[register]: https://docs.gitlab.com/runner/register/
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index befaa06e918..cf25a8b618f 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -34,9 +34,9 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a
passphrase to the SSH key, or the `before_script` will prompt for it.
Then, create a new **Secret Variable** in your project settings on GitLab
-following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
-and in the **Value** field paste the content of your _private_ key that you
-created earlier.
+following **Settings > Pipelines** and look for the "Secret Variables" section.
+As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
+content of your _private_ key that you created earlier.
It is also good practice to check the server's own public key to make sure you
are not being targeted by a man-in-the-middle attack. To do this, add another
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index d1f9881e51b..22e7f6879ed 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -9,8 +9,9 @@ and a list of **user-defined variables**.
The variables can be overwritten and they take precedence over each other in
this order:
-1. [Trigger variables][triggers] (take precedence over all)
-1. [Secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
+1. [Trigger variables][triggers] or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables) (take precedence over all)
+1. Project-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
+1. Group-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
1. YAML-defined [global variables](../yaml/README.md#variables)
1. [Deployment variables](#deployment-variables)
@@ -37,9 +38,10 @@ future GitLab releases.**
|-------------------------------- |--------|--------|-------------|
| **CI** | all | 0.4 | Mark that job is executed in CI environment |
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
-| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
@@ -141,25 +143,30 @@ script:
>**Notes:**
- This feature requires GitLab Runner 0.4.0 or higher.
+- Group-level secret variables added in GitLab 9.4.
- Be aware that secret variables are not masked, and their values can be shown
in the job logs if explicitly asked to do so. If your project is public or
internal, you can set the pipelines private from your project's Pipelines
settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables.
-GitLab CI allows you to define per-project **secret variables** that are set in
-the build environment. The secret variables are stored out of the repository
-(`.gitlab-ci.yml`) and are securely passed to GitLab Runner making them
-available in the build environment. It's the recommended method to use for
-storing things like passwords, secret keys and credentials.
+GitLab CI allows you to define per-project or per-group **secret variables**
+that are set in the build environment. The secret variables are stored out of
+the repository (`.gitlab-ci.yml`) and are securely passed to GitLab Runner
+making them available in the build environment. It's the recommended method to
+use for storing things like passwords, secret keys and credentials.
-Secret variables can be added by going to your project's
-**Settings ➔ Pipelines**, then finding the section called
-**Secret variables**.
+Project-level secret variables can be added by going to your project's
+**Settings ➔ Pipelines**, then finding the section called **Secret variables**.
-Once you set them, they will be available for all subsequent pipelines.
+Likewise, group-level secret variables can be added by going to your group's
+**Settings ➔ Pipelines**, then finding the section called **Secret variables**.
+Any variables of [subgroups] will be inherited recursively.
+
+Once you set them, they will be available for all subsequent pipelines. You can also
+[protect your variables](#protected-secret-variables).
-## Protected secret variables
+### Protected secret variables
>**Notes:**
This feature requires GitLab 9.3 or higher.
@@ -425,10 +432,12 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
```
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784
-[runner]: https://docs.gitlab.com/runner/
-[triggered]: ../triggers/README.md
-[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
+[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium"
+[envs]: ../environments.md
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
+[runner]: https://docs.gitlab.com/runner/
[shellexecutors]: https://docs.gitlab.com/runner/executors/
-[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium"
+[triggered]: ../triggers/README.md
+[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
+[subgroups]: ../../user/group/subgroups/index.md
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 8a0662db6fd..724843a4d56 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -306,6 +306,53 @@ cache:
untracked: true
```
+### cache:policy
+
+> Introduced in GitLab 9.4.
+
+The default behaviour of a caching job is to download the files at the start of
+execution, and to re-upload them at the end. This allows any changes made by the
+job to be persisted for future runs, and is known as the `pull-push` cache
+policy.
+
+If you know the job doesn't alter the cached files, you can skip the upload step
+by setting `policy: pull` in the job specification. Typically, this would be
+twinned with an ordinary cache job at an earlier stage to ensure the cache
+is updated from time to time:
+
+```yaml
+stages:
+ - setup
+ - test
+
+prepare:
+ stage: setup
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ script:
+ - bundle install --deployment
+
+rspec:
+ stage: test
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ policy: pull
+ script:
+ - bundle exec rspec ...
+```
+
+This helps to speed up job execution and reduce load on the cache server,
+especially when you have a large number of cache-using jobs executing in
+parallel.
+
+Additionally, if you have a job that unconditionally recreates the cache without
+reference to its previous contents, you can use `policy: push` in that job to
+skip the download step.
+
## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
diff --git a/doc/development/README.md b/doc/development/README.md
index 9496a87d84d..58993c52dcd 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -54,6 +54,8 @@
- [Polymorphic Associations](polymorphic_associations.md)
- [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md)
+- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
+- [Iterating Tables In Batches](iterating_tables_in_batches.md)
## i18n
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index 0239e6b3163..72a34aa7de9 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -50,14 +50,13 @@ your migration:
BackgroundMigrationWorker.perform_async('BackgroundMigrationClassName', [arg1, arg2, ...])
```
-Usually it's better to schedule jobs in bulk, for this you can use
+Usually it's better to enqueue jobs in bulk, for this you can use
`BackgroundMigrationWorker.perform_bulk`:
```ruby
BackgroundMigrationWorker.perform_bulk(
- ['BackgroundMigrationClassName', [1]],
- ['BackgroundMigrationClassName', [2]],
- ...
+ [['BackgroundMigrationClassName', [1]],
+ ['BackgroundMigrationClassName', [2]]]
)
```
@@ -68,6 +67,16 @@ consuming migrations it's best to schedule a background job using an
updates. Removals in turn can be handled by simply defining foreign keys with
cascading deletes.
+If you would like to schedule jobs in bulk with a delay, you can use
+`BackgroundMigrationWorker.perform_bulk_in`:
+
+```ruby
+jobs = [['BackgroundMigrationClassName', [1]],
+ ['BackgroundMigrationClassName', [2]]]
+
+BackgroundMigrationWorker.perform_bulk_in(5.minutes, jobs)
+```
+
## Cleaning Up
Because background migrations can take a long time you can't immediately clean
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 5c6316b9ac6..59e8a087e02 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -3,5 +3,19 @@
Starting from GitLab 9.3 we support feature flags via
[Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature`
class (defined in `lib/feature.rb`) in your code to get, set and list feature
-flags. During runtime you can set the values for the gates via the
-[admin API](../api/features.md).
+flags.
+
+During runtime you can set the values for the gates via the
+[features API](../api/features.md) (accessible to admins only).
+
+## Feature groups
+
+Starting from GitLab 9.4 we support feature groups via
+[Flipper groups](https://github.com/jnunemaker/flipper/blob/v0.10.2/docs/Gates.md#2-group).
+
+Feature groups must be defined statically in `lib/feature.rb` (in the
+`.register_feature_groups` method), but their implementation can obviously be
+dynamic (querying the DB etc.).
+
+Once defined in `lib/feature.rb`, you will be able to activate a
+feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
diff --git a/doc/development/iterating_tables_in_batches.md b/doc/development/iterating_tables_in_batches.md
new file mode 100644
index 00000000000..590c8cbba2d
--- /dev/null
+++ b/doc/development/iterating_tables_in_batches.md
@@ -0,0 +1,37 @@
+# Iterating Tables In Batches
+
+Rails provides a method called `in_batches` that can be used to iterate over
+rows in batches. For example:
+
+```ruby
+User.in_batches(of: 10) do |relation|
+ relation.update_all(updated_at: Time.now)
+end
+```
+
+Unfortunately this method is implemented in a way that is not very efficient,
+both query and memory usage wise.
+
+To work around this you can include the `EachBatch` module into your models,
+then use the `each_batch` class method. For example:
+
+```ruby
+class User < ActiveRecord::Base
+ include EachBatch
+end
+
+User.each_batch(of: 10) do |relation|
+ relation.update_all(updated_at: Time.now)
+end
+```
+
+This will end up producing queries such as:
+
+```
+User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
+ (0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
+```
+
+The API of this method is similar to `in_batches`, though it doesn't support
+all of the arguments that `in_batches` supports. You should always use
+`each_batch` _unless_ you have a specific need for `in_batches`.
diff --git a/doc/development/policies.md b/doc/development/policies.md
new file mode 100644
index 00000000000..62141356f59
--- /dev/null
+++ b/doc/development/policies.md
@@ -0,0 +1,116 @@
+# `DeclarativePolicy` framework
+
+The DeclarativePolicy framework is designed to assist in performance of policy checks, and to enable ease of extension for EE. The DSL code in `app/policies` is what `Ability.allowed?` uses to check whether a particular action is allowed on a subject.
+
+The policy used is based on the subject's class name - so `Ability.allowed?(user, :some_ability, project)` will create a `ProjectPolicy` and check permissions on that.
+
+## Managing Permission Rules
+
+Permissions are broken into two parts: `conditions` and `rules`. Conditions are boolean expressions that can access the database and the environment, while rules are statically configured combinations of expressions and other rules that enable or prevent certain abilities. For an ability to be allowed, it must be enabled by at least one rule, and not prevented by any.
+
+
+### Conditions
+
+Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class.
+
+``` ruby
+class FooPolicy < BasePolicy
+ condition(:is_public) do
+ # @subject guaranteed to be an instance of Foo
+ @subject.public?
+ end
+
+ # instance methods can be called from the condition as well
+ condition(:thing) { check_thing }
+
+ def check_thing
+ # ...
+ end
+end
+```
+
+When you define a condition, a predicate method is defined on the policy to check whether that condition passes - so in the above example, an instance of `FooPolicy` will also respond to `#is_public?` and `#thing?`.
+
+Conditions are cached according to their scope. Scope and ordering will be covered later.
+
+### Rules
+
+A `rule` is a logical combination of conditions and other rules, that are configured to enable or prevent certain abilities. It is important to note that the rule configuration is static - a rule's logic cannot touch the database or know about `@user` or `@subject`. This allows us to cache only at the condition level. Rules are specified through the `rule` method, which takes a block of DSL configuration, and returns an object that responds to `#enable` or `#prevent`:
+
+``` ruby
+class FooPolicy < BasePolicy
+ # ...
+
+ rule { is_public }.enable :read
+ rule { thing }.prevent :read
+
+ # equivalently,
+ rule { is_public }.policy do
+ enable :read
+ end
+
+ rule { ~thing }.policy do
+ prevent :read
+ end
+end
+```
+
+Within the rule DSL, you can use:
+
+* A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
+* `~` indicates negation
+* `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`
+* `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
+
+## Scores, Order, Performance
+
+To see how the rules get evaluated into a judgment, it is useful in a console to use `policy.debug(:some_ability)`. This will print the rules in the order they are evaluated.
+
+When a policy is asked whether a particular ability is allowed (`policy.allowed?(:some_ability)`), it does not necessarily have to compute all the conditions on the policy. First, only the rules relevant to that particular ability are selected. Then, the execution model takes advantage of short-circuiting, and attempts to sort rules based on a heuristic of how expensive they will be to calculate. The sorting is dynamic and cache-aware, so that previously calculated conditions will be considered first, before computing other conditions.
+
+## Scope
+
+Sometimes, a condition will only use data from `@user` or only from `@subject`. In this case, we want to change the scope of the caching, so that we don't recalculate conditions unnecessarily. For example, given:
+
+``` ruby
+class FooPolicy < BasePolicy
+ condition(:expensive_condition) { @subject.expensive_query? }
+
+ rule { expensive_condition }.enable :some_ability
+end
+```
+
+Naively, if we call `Ability.can?(user1, :some_ability, foo)` and `Ability.can?(user2, :some_ability, foo)`, we would have to calculate the condition twice - since they are for different users. But if we use the `scope: :subject` option:
+
+``` ruby
+ condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }
+```
+
+then the result of the condition will be cached globally only based on the subject - so it will not be calculated repeatedly for different users. Similarly, `scope: :user` will cache only based on the user.
+
+**DANGER**: If you use a `:scope` option when the condition actually uses data from
+both user and subject (including a simple anonymous check!) your result will be cached at too global of a scope and will result in cache bugs.
+
+Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
+
+``` ruby
+def users_that_can_read_project(users, project)
+ DeclarativePolicy.subject_scope do
+ users.select { |u| allowed?(u, :read_project, project) }
+ end
+end
+```
+
+This will, for example, prefer checking `project.public?` to checking `user.admin?`.
+
+## Delegation
+
+Delegation is the inclusion of rules from another policy, on a different subject. For example,
+
+``` ruby
+class FooPolicy < BasePolicy
+ delegate { @subject.project }
+end
+```
+
+will include all rules from `ProjectPolicy`. The delegated conditions will be evaluated with the correct delegated subject, and will be sorted along with the regular rules in the policy. Note that only the relevant rules for a particular ability will actually be considered.
diff --git a/doc/development/sha1_as_binary.md b/doc/development/sha1_as_binary.md
new file mode 100644
index 00000000000..3151cc29bbc
--- /dev/null
+++ b/doc/development/sha1_as_binary.md
@@ -0,0 +1,36 @@
+# Storing SHA1 Hashes As Binary
+
+Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
+requires at least 40 bytes, an additional byte to store the encoding, and
+perhaps more space depending on the internals of PostgreSQL and MySQL.
+
+On the other hand, if one were to store a SHA1 as binary one would only need 20
+bytes for the actual SHA1, and 1 or 4 bytes of additional space (again depending
+on database internals). This means that in the best case scenario we can reduce
+the space usage by 50%.
+
+To make this easier to work with you can include the concern `ShaAttribute` into
+a model and define a SHA attribute using the `sha_attribute` class method. For
+example:
+
+```ruby
+class Commit < ActiveRecord::Base
+ include ShaAttribute
+
+ sha_attribute :sha
+end
+```
+
+This allows you to use the value of the `sha` attribute as if it were a string,
+while storing it as binary. This means that you can do something like this,
+without having to worry about converting data to the right binary format:
+
+```ruby
+commit = Commit.find_by(sha: '88c60307bd1f215095834f09a1a5cb18701ac8ad')
+commit.sha = '971604de4cfa324d91c41650fabc129420c8d1cc'
+commit.save
+```
+
+There is however one requirement: the column used to store the SHA has _must_ be
+a binary type. For Rails this means you need to use the `:binary` type instead
+of `:text` or `:string`.
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
index fe4b6d73771..75bae324585 100644
--- a/doc/downgrade_ee_to_ce/README.md
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -46,6 +46,19 @@ $ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDepre
$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production
```
+### Secret variables environment scopes
+
+If you're using this feature and there are variables sharing the same
+key, but they have different scopes in a project, then you might want to
+revisit the environment scope setting for those variables.
+
+In CE, environment scopes are completely ignored, therefore you could
+accidentally get a variable which you're not expecting for a particular
+environment. Make sure that you have the right variables in this case.
+
+Data is completely preserved, so you could always upgrade back to EE and
+restore the behavior if you leave it alone.
+
## Downgrade to CE
After performing the above mentioned steps, you are now ready to downgrade your
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 9a171d34671..bc75dc1447e 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -39,11 +39,14 @@ mysql> SET storage_engine=INNODB;
# If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
+# If you use MySQL with replication, or just have MySQL configured with binary logging, you need to run the following to allow the use of `TRIGGER`:
+mysql> SET GLOBAL log_bin_trust_function_creators = 1;
+
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Grant the GitLab user necessary permissions on the database
-mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
@@ -60,7 +63,15 @@ mysql> \q
```
You are done installing the database for now and can go back to the rest of the installation.
-Please proceed to the rest of the installation before running through the utf8mb4 support section.
+Please proceed to the rest of the installation **before** running through the steps below.
+
+### `log_bin_trust_function_creators`
+
+If you use MySQL with replication, or just have MySQL configured with binary logging, all of your MySQL servers will need to have `log_bin_trust_function_creators` enabled to allow the use of `TRIGGER` in migrations. You have already set this global variable in the steps above, but to make it persistent, add the following to your `my.cnf` file:
+
+```
+log_bin_trust_function_creators=1
+```
### MySQL utf8mb4 support
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 84af6432889..dfa25deb961 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -294,9 +294,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-2-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-3-stable gitlab
-**Note:** You can change `9-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `9-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -420,6 +420,12 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up GitLab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
+**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners:
+
+* Export `RUBYOPT=--disable-gems` environment variable for the processes
+* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommened for system-wide Ruby.
+* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707)
+
### Install gitlab-workhorse
GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index b8bc0795f2e..515b2841d08 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -54,6 +54,13 @@ gitlabURL: http://gitlab.your-domain.com/
##
runnerRegistrationToken: ""
+## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use
+## Provide resource name for a Kubernetes Secret Object in the same namespace,
+## this is used to populate the /etc/gitlab-runner/certs directory
+## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
+##
+#certsSecretName:
+
## Configure the maximum number of concurrent jobs
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
@@ -135,6 +142,52 @@ runners:
privileged: true
```
+### Providing a custom certificate for accessing GitLab
+
+You can provide a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
+to the GitLab Runner Helm Chart, which will be used to populate the container's
+`/etc/gitlab-runner/certs` directory.
+
+Each key name in the Secret will be used as a filename in the directory, with the
+file content being the value associated with the key.
+
+More information on how GitLab Runner uses these certificates can be found in the
+[Runner Documentation](https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates).
+
+ - The key/file name used should be in the format `<gitlab-hostname>.crt`. For example: `gitlab.your-domain.com.crt`.
+ - Any intermediate certificates need to be concatenated to your server certificate in the same file.
+ - The hostname used should be the one the certificate is registered for.
+
+The GitLab Runner Helm Chart does not create a secret for you. In order to create
+the secret, you can prepare your certificate on you local machine, and then run
+the `kubectl create secret` command from the directory with the certificate
+
+```bash
+kubectl
+ --namespace <NAMESPACE>
+ create secret generic <SECRET_NAME>
+ --from-file=<CERTFICATE_FILENAME>
+```
+
+- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner.
+- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
+- `<CERTFICATE_FILENAME>` is the filename for the certificate in your current directory that will be imported into the secret
+
+You then need to provide the secret's name to the GitLab Runner chart.
+
+Add the following to your `values.yaml`
+
+```yaml
+## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use
+## Provide resource name for a Kubernetes Secret Object in the same namespace,
+## this is used to populate the /etc/gitlab-runner/certs directory
+## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
+##
+certsSecretName: <SECRET NAME>
+```
+
+- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
+
## Installing GitLab Runner using the Helm Chart
Once you [have configured](#configuration) GitLab Runner in your `values.yml` file,
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 265c891cf83..2dd9b33273c 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -8,6 +8,9 @@ you to do the following:
issue index of the external tracker
- clicking **New issue** on the project dashboard creates a new issue on the
external tracker
+- you can reference these external issues inside GitLab interface
+ (merge requests, commits, comments) and they will be automatically converted
+ into links
## Configuration
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index d6b2f11d49a..42132f690d8 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -156,7 +156,7 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-
+
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index e7d97fde14e..225a4dcc924 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -70,7 +70,27 @@ curl --location https://yarnpkg.com/install.sh | bash -
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
-### 5. Get latest code
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
```bash
cd /home/git/gitlab
@@ -97,7 +117,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout 9-2-stable-ee
```
-### 6. Update gitlab-shell
+### 7. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -107,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 7. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
-Install and compile gitlab-workhorse. This requires
-[Go 1.8](https://golang.org/dl). Go (at least 1.5) should already be on your system from
-GitLab 8.1 and shall be upgraded if necessary. Please note that starting in Gitlab 9.3, only Go 1.8.3 and above will be supported. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -123,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 8. Update configuration files
+### 9. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -197,7 +216,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 9. Install libs, migrations, etc.
+### 10. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -223,7 +242,7 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
-### 10. Optional: install Gitaly
+### 11. Optional: install Gitaly
Gitaly is still an optional component of GitLab. If you want to save time
during your 9.2 upgrade **you can skip this step**.
@@ -240,14 +259,14 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 11. Start application
+### 12. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 12. Check application status
+### 13. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 0c32e4db53f..910539acc70 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
### 5. Update Go
-NOTE: GitLab 9.3 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be
-sure to upgrade your installation if necessary
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
You can check which version you are running with `go version`.
@@ -117,7 +117,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout 9-3-stable-ee
```
-### 5. Update gitlab-shell
+### 7. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -127,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 6. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
-Install and compile gitlab-workhorse. This requires
-[Go 1.5](https://golang.org/dl) which should already be on your system from
-GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -143,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 7. Update Gitaly
+### 9. Update Gitaly
If you have not yet set up Gitaly then follow [Gitaly section of the installation
guide](../install/installation.md#install-gitaly).
@@ -157,7 +156,29 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update configuration files
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -231,7 +252,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 11. Install libs, migrations, etc.
+### 12. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -257,14 +278,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
-### 12. Start application
+### 13. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 13. Check application status
+### 14. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index a712ce5a8b1..bbb7f4a8d48 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -148,6 +148,8 @@ sudo -u git -H make
If you have not yet set up Gitaly then follow [Gitaly section of the installation
guide](../install/installation.md#install-gitaly).
+As of GitLab 9.4, Gitaly is a mandatory component of GitLab.
+
#### Check Gitaly configuration
Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 9488ce1ef30..f28c034e74c 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -14,6 +14,9 @@ accepted method of authentication when you have
Once you have your token, [pass it to the API][usage] using either the
`private_token` parameter or the `PRIVATE-TOKEN` header.
+The expiration of personal access tokens happens on the date you define,
+at midnight UTC.
+
## Creating a personal access token
You can create as many personal access tokens as you like from your GitLab
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index b636cb294b8..cf7f519f783 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png
index cdfc466d23f..973d9f7cde4 100644
--- a/doc/user/project/img/issue_board_add_list.png
+++ b/doc/user/project/img/issue_board_add_list.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_move_issue_card_list.png b/doc/user/project/img/issue_board_move_issue_card_list.png
new file mode 100644
index 00000000000..c6b17ada40e
--- /dev/null
+++ b/doc/user/project/img/issue_board_move_issue_card_list.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png
index 5318e6ea4a9..127b9b08cc7 100644
--- a/doc/user/project/img/issue_board_welcome_message.png
+++ b/doc/user/project/img/issue_board_welcome_message.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png
index 33049dce74f..bedaf724a15 100644
--- a/doc/user/project/img/issue_boards_add_issues_modal.png
+++ b/doc/user/project/img/issue_boards_add_issues_modal.png
Binary files differ
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 0b219e84478..6a040516231 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -16,3 +16,14 @@ Once you have configured and enabled Bugzilla:
- the **Issues** link on the GitLab project pages takes you to the appropriate
Bugzilla product page
- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
+
+## Referencing issues in Bugzilla
+
+Issues in Bugzilla can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+ then followed by capital letters, numbers or underscores, and `<ID>` is
+ a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index 73fa83d72a8..bfe2672e098 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -55,6 +55,7 @@ GitLab CI build environment:
- `KUBE_CA_PEM_FILE` - only present if a custom CA bundle was specified. Path
to a file containing PEM data.
- `KUBE_CA_PEM` (deprecated)- only if a custom CA bundle was specified. Raw PEM data.
+- `KUBECONFIG` - Path to a file containing kubeconfig for this deployment. CA bundle would be embedded if specified.
## Web terminals
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 89c0312d3c2..8026f1f57bc 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -21,3 +21,14 @@ Once you have configured and enabled Redmine:
As an example, below is a configuration for a project named gitlab-ci.
![Redmine configuration](img/redmine_configuration.png)
+
+## Referencing issues in Redmine
+
+Issues in Redmine can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+ then followed by capital letters, numbers or underscores, and `<ID>` is
+ a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index ebea7062ecb..e2cc67726e0 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -1,8 +1,7 @@
-# Issue board
+# Issue Board
->**Notes:**
-- [Introduced][ce-5554] in GitLab 8.11.
-- The Backlog column was replaced by the **Add issues** button in GitLab 8.17.
+>**Note:**
+[Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board).
The GitLab Issue Board is a software project management tool used to plan,
organize, and visualize a workflow for a feature or product release.
@@ -15,12 +14,65 @@ Other interesting links:
## Overview
-The Issue Board builds on GitLab's existing issue tracking functionality and
+The Issue Board builds on GitLab's existing
+[issue tracking functionality](issues/index.md#issue-tracker) and
leverages the power of [labels] by utilizing them as lists of the scrum board.
-With the Issue Board you can have a different view of your issues while also
+With the Issue Board you can have a different view of your issues while
maintaining the same filtering and sorting abilities you see across the
-issue tracker.
+issue tracker. An Issue Board is based on its project's label structure, therefore, it
+applies the same descriptive labels to indicate placement on the board, keeping
+consistency throughout the entire development lifecycle.
+
+An Issue Board shows you what issues your team is working on, who is assigned to each,
+and where in the workflow those issues are.
+
+You create issues, host code, perform reviews, build, test,
+and deploy from one single platform. Issue Boards help you to visualize
+and manage the entire process _in_ GitLab.
+
+With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available
+only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/),
+you go even further, as you can not only keep yourself and your project
+organized from a broader perspective with one Issue Board per project,
+but also allow your team members to organize their own workflow by creating
+multiple Issue Boards within the same project.
+
+## Use cases
+
+GitLab Workflow allows you to discuss proposals in issues, categorize them
+with labels, and from there organize and prioritize them with Issue Boards.
+
+For example, let's consider this simplified development workflow:
+
+1. You have a repository hosting your app's codebase
+and your team actively contributing to code
+1. Your **backend** team starts working a new
+implementation, gathers feedback and approval, and pass it over to **frontend**
+1. When frontend is complete, the new feature is deployed to **staging** to be tested
+1. When successful, it is deployed to **production**
+
+If we have the labels "**backend**", "**frontend**", "**staging**", and
+"**production**", and an Issue Board with a list for each, we can:
+
+- Visualize the entire flow of implementations since the
+beginning of the development lifecycle until deployed to production
+- Prioritize the issues in a list by moving them vertically
+- Move issues between lists to organize them according to the labels you've set
+- Add multiple issues to lists in the board by selecting one or more existing issues
+
+![issue card moving](img/issue_board_move_issue_card_list.png)
+
+> **Notes:**
+>
+>- For a broader use case, please check the blog post
+[GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+>
+>- For a real use case, please check why
+[Codepen decided to adopt Issue Boards](https://about.gitlab.com/2017/01/27/codepen-welcome-to-gitlab/#project-management-everything-in-one-place)
+to improve their workflow with [multiple boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+
+## Issue Board terminology
Below is a table of the definitions used for GitLab's Issue Board.
@@ -57,7 +109,7 @@ In short, here's a list of actions you can take in an Issue Board:
If you are not able to perform one or more of the things above, make sure you
have the right [permissions](#permissions).
-## First time using the issue board
+## First time using the Issue Board
The first time you navigate to your Issue Board, you will be presented with
a default list (**Done**) and a welcoming message that gives
@@ -98,7 +150,7 @@ list view that is removed. You can always add it back later if you need.
## Adding issues to a list
You can add issues to a list by clicking the **Add issues** button that is
-present in the upper right corner of the issue board. This will open up a modal
+present in the upper right corner of the Issue Board. This will open up a modal
window where you can see all the issues that do not belong to any list.
Select one or more issues by clicking on the cards and then click **Add issues**
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index fe87e6f9495..e55e2aea023 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -1,4 +1,4 @@
-# Issues documentation
+# Issues
The GitLab Issue Tracker is an advanced and complete tool
for tracking the evolution of a new idea or the process
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 99233ed5ae2..1848514e2dd 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -21,14 +21,11 @@ Once you fill in all the details, hit the **Create milestone** button.
>**Note:**
You need [Master permissions](../../permissions.md) in order to create a milestone.
-You can create a milestone for several projects in the same group simultaneously.
-On the group's **Issues ➔ Milestones** page, you will be able to see the status
-of that milestone across all of the selected projects. To create a new milestone
-for selected projects in the group, click the **New milestone** button. The
-form is the same as when creating a milestone for a specific project with the
-addition of the selection of the projects you want to inherit this milestone.
-
-![Creating a group milestone](img/milestone_group_create.png)
+You can create a milestone for a group that will be shared across group projects.
+On the group's **Issues ➔ Milestones** page, you will be able to see the state
+of that milestone and the issues/merge requests count that it shares across the group projects. To create a new milestone click the **New milestone** button. The form is the same as when creating a milestone for a specific project which you can find in the previous item.
+
+In addition to that you will be able to filter issues or merge requests by group milestones in all projects that belongs to the milestone group.
## Special milestone filters
diff --git a/doc/user/project/pipelines/img/pipeline_schedule_variables.png b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
new file mode 100644
index 00000000000..47a0c6f3697
--- /dev/null
+++ b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
index ea5394fa8a6..5a0e5965992 100644
--- a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
Binary files differ
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 17cc21238ff..258b3a2f955 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -31,6 +31,15 @@ is installed on.
![Schedules list](img/pipeline_schedules_list.png)
+### Making use of scheduled pipeline variables
+
+> [Introduced][ce-12328] in GitLab 9.4.
+
+You can pass any number of arbitrary variables and they will be available in
+GitLab CI so that they can be used in your `.gitlab-ci.yml` file.
+
+![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
+
## Using only and except
To configure that a job can be executed only when the pipeline has been
@@ -79,4 +88,5 @@ don't have admin access to the server, ask your administrator.
[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
+[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328
[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index a992a348c0f..3ff5a08d72c 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -27,6 +27,22 @@ The default value is 60 minutes. Decrease the time limit if you want to impose
a hard limit on your jobs' running time or increase it otherwise. In any case,
if the job surpasses the threshold, it is marked as failed.
+## Custom CI config path
+
+> - [Introduced][ce-12509] in GitLab 9.4.
+
+By default we look for the `.gitlab-ci.yml` file in the project's root
+directory. If you require a different location **within** the repository,
+you can set a custom filepath that will be used to lookup the config file,
+this filepath should be **relative** to the root.
+
+Here are some valid examples:
+
+> * .gitlab-ci.yml
+> * .my-custom-file.yml
+> * my/path/.gitlab-ci.yml
+> * my/path/.my-custom-file.yml
+
## Test coverage parsing
If you use test coverage in your code, GitLab can capture its output in the
@@ -59,8 +75,8 @@ pipelines** checkbox and save the changes.
> [Introduced][ce-9362] in GitLab 9.1.
-If you want to auto-cancel all pending non-HEAD pipelines on branch, when
-new pipeline will be created (after your git push or manually from UI),
+If you want to auto-cancel all pending non-HEAD pipelines on branch, when
+new pipeline will be created (after your git push or manually from UI),
check **Auto-cancel pending pipelines** checkbox and save the changes.
## Badges
@@ -115,3 +131,4 @@ into your `README.md`:
[var]: ../../../ci/yaml/README.md#git-strategy
[coverage report]: #test-coverage-parsing
[ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362
+[ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index e10ccc4fc46..ea28968fbb2 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -300,7 +300,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
If you have long lived feature branches that last for more than a few days you should make your issues smaller.
-## Working wih feature branches
+## Working with feature branches
![Shell output showing git pull output](git_pull.png)
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
deleted file mode 100644
index 046e2815d4e..00000000000
--- a/features/dashboard/new_project.feature
+++ /dev/null
@@ -1,30 +0,0 @@
-@dashboard
-Feature: New Project
-Background:
- Given I sign in as a user
- And I own project "Shop"
- And I visit dashboard page
- And I click "New project" link
-
- @javascript
- Scenario: I should see New Projects page
- Then I see "New Project" page
- Then I see all possible import options
-
- @javascript
- Scenario: I should see instructions on how to import from Git URL
- Given I see "New Project" page
- When I click on "Repo by URL"
- Then I see instructions on how to import from Git URL
-
- @javascript
- Scenario: I should see instructions on how to import from GitHub
- Given I see "New Project" page
- When I click on "Import project from GitHub"
- Then I am redirected to the GitHub import page
-
- @javascript
- Scenario: I should see Google Code import page
- Given I see "New Project" page
- When I click on "Google Code"
- Then I redirected to Google Code import page
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
index 1c1539b3e12..2211acfee20 100644
--- a/features/group/milestones.feature
+++ b/features/group/milestones.feature
@@ -22,12 +22,12 @@ Feature: Group Milestones
Then I should see group milestone with descriptions and expiry date
And I should see group milestone with all issues and MRs assigned to that milestone
- Scenario: Create multiple milestones with one form
+ Scenario: Create group milestones
Given I visit group "Owned" milestones page
And I click new milestone button
And I fill milestone name
When I press create mileston button
- Then milestone in each project should be created
+ Then group milestone should be created
Scenario: I should see Issues listed with labels
Given Group has projects with milestones
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 34201cd8486..3ea0aab5a67 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -31,6 +31,11 @@ Feature: Project Active Tab
Then the active main tab should be Wiki
And no other main tabs should be active
+ Scenario: On Project Members
+ Given I visit my project's members page
+ Then the active main tab should be Members
+ And no other main tabs should be active
+
# Sub Tabs: Home
Scenario: On Project Home/Show
@@ -63,12 +68,6 @@ Feature: Project Active Tab
And no other sub tabs should be active
And the active main tab should be Settings
- Scenario: On Project Members
- Given I visit my project's members page
- Then the active sub tab should be Members
- And no other sub tabs should be active
- And the active main tab should be Settings
-
# Sub Tabs: Repository
Scenario: On Project Repository/Files
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 472ec9544f3..59a625056d6 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -131,16 +131,6 @@ Feature: Project Source Browse Files
Then I can see the new text file
@javascript
- Scenario: If I enter an illegal file name I see an error message
- Given I click on "New file" link in repo
- And I fill the new file name with an illegal name
- And I edit code
- And I fill the commit message
- And I click on "Commit changes"
- Then I am on the new file page
- And I see "Path can contain only..."
-
- @javascript
Scenario: I can create file with a directory name
Given I click on "New file" link in repo
And I fill the new file name with a new directory
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
deleted file mode 100644
index 1ad02780229..00000000000
--- a/features/snippets/snippets.feature
+++ /dev/null
@@ -1,40 +0,0 @@
-@snippets
-Feature: Snippets
- Background:
- Given I sign in as a user
- And I have public "Personal snippet one" snippet
- And I have private "Personal snippet private" snippet
-
- @javascript
- Scenario: I create new snippet
- Given I visit new snippet page
- And I submit new snippet "Personal snippet three"
- Then I should see snippet "Personal snippet three"
-
- Scenario: I update "Personal snippet one"
- Given I visit snippet page "Personal snippet one"
- And I click link "Edit"
- And I submit new title "Personal snippet new title"
- Then I should see "Personal snippet new title"
-
- Scenario: Set "Personal snippet one" public
- Given I visit snippet page "Personal snippet one"
- And I click link "Edit"
- And I uncheck "Private" checkbox
- Then I should see "Personal snippet one" public
-
- Scenario: I destroy "Personal snippet one"
- Given I visit snippet page "Personal snippet one"
- And I click link "Delete"
- Then I should not see "Personal snippet one" in snippets
-
- Scenario: I create new internal snippet
- Given I logout directly
- And I sign in as an admin
- Then I visit new snippet page
- And I submit new internal snippet
- Then I visit snippet page "Internal personal snippet one"
- And I logout directly
- Then I sign in as a user
- Given I visit new snippet page
- Then I visit snippet page "Internal personal snippet one"
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 71c69a4fdea..0960f49aad3 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -27,7 +27,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I see prefilled new Merge Request page' do
expect(page).to have_selector('.merge-request-form')
- expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
+ expect(current_path).to eq project_new_merge_request_path(@project)
expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix"
expect(find("input#merge_request_target_branch").value).to eq "master"
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
deleted file mode 100644
index 530fd6f7bdb..00000000000
--- a/features/steps/dashboard/new_project.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-class Spinach::Features::NewProject < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'I click "New project" link' do
- page.within '#content-body' do
- click_link "New project"
- end
- end
-
- step 'I click "New project" in top right menu' do
- page.within '.header-content' do
- click_link "New project"
- end
- end
-
- step 'I see "New Project" page' do
- expect(page).to have_content('Project path')
- expect(page).to have_content('Project name')
- end
-
- step 'I see all possible import options' do
- expect(page).to have_link('GitHub')
- expect(page).to have_link('Bitbucket')
- expect(page).to have_link('GitLab.com')
- expect(page).to have_link('Google Code')
- expect(page).to have_button('Repo by URL')
- expect(page).to have_link('GitLab export')
- end
-
- step 'I click on "Import project from GitHub"' do
- first('.import_github').click
- end
-
- step 'I am redirected to the GitHub import page' do
- expect(page).to have_content('Import Projects from GitHub')
- expect(current_path).to eq new_import_github_path
- end
-
- step 'I click on "Repo by URL"' do
- first('.import_git').click
- end
-
- step 'I see instructions on how to import from Git URL' do
- git_import_instructions = first('.js-toggle-content')
- expect(git_import_instructions).to be_visible
- expect(git_import_instructions).to have_content "Git repository URL"
- end
-
- step 'I click on "Google Code"' do
- first('.import_google_code').click
- end
-
- step 'I redirected to Google Code import page' do
- expect(page).to have_content('Import projects from Google Code')
- expect(current_path).to eq new_import_google_code_path
- end
-end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
deleted file mode 100644
index c33813e550b..00000000000
--- a/features/steps/dashboard/starred_projects.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'I starred project "Community"' do
- current_user.toggle_star(Project.find_by(name: 'Community'))
- end
-
- step 'I should not see project "Shop"' do
- page.within '.projects-list' do
- expect(page).not_to have_content('Shop')
- end
- end
-end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 1a55f40abb9..f1288c15084 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -66,7 +66,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New feature",
project: public_project
)
- visit namespace_project_issues_path(public_project.namespace, public_project)
+ visit project_issues_path(public_project)
end
step 'I should see list of issues for "Community" project' do
@@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New internal feature",
project: internal_project
)
- visit namespace_project_issues_path(internal_project.namespace, internal_project)
+ visit project_issues_path(internal_project)
end
step 'I should see list of issues for "Internal" project' do
@@ -94,7 +94,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Community" merge requests page' do
- visit namespace_project_merge_requests_path(public_project.namespace, public_project)
+ visit project_merge_requests_path(public_project)
end
step 'project "Community" has "Bug fix" open merge request' do
@@ -111,7 +111,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Internal" merge requests page' do
- visit namespace_project_merge_requests_path(internal_project.namespace, internal_project)
+ visit project_merge_requests_path(internal_project)
end
step 'project "Internal" has "Feature implemented" open merge request' do
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 0542b06c0ab..915d766dd60 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -39,7 +39,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
expect(page).to have_content('Milestone GL-113')
expect(page).to have_content('Issues 3 Open: 3 Closed: 0')
issue = Milestone.find_by(name: 'GL-113').issues.first
- expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
+ expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end
step 'I fill milestone name' do
@@ -54,14 +54,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
click_button "Create milestone"
end
- step 'milestone in each project should be created' do
+ step 'group milestone should be created' do
group = Group.find_by(name: 'Owned')
- expect(page).to have_content "Milestone v2.9.0"
- expect(group.projects).to be_present
-
- group.projects.each do |project|
- expect(page).to have_content project.name
- end
+ expect(page).to have_content group.milestones.find_by_title('v2.9.0').title
end
step 'I should see the "bug" label' do
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index b6f1d417e21..e4847180be9 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
When 'I visit project "Forum" page' do
project = Project.find_by(name: "Forum")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I should not see "Archived"' do
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
index 96c59322f9b..5a9094ee9d3 100644
--- a/features/steps/project/badges/build.rb
+++ b/features/steps/project/badges/build.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
include RepoHelpers
step 'I display builds badge for a master branch' do
- visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
+ visit build_project_badges_path(@project, ref: :master, format: :svg)
end
step 'I should see a build success badge' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index f19fa1c7600..305fff37c41 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I see commit info' do
@@ -73,7 +73,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit commits list page for feature branch' do
- visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
+ visit project_commits_path(@project, 'feature', { limit: 5 })
end
step 'I see feature branch commits' do
@@ -119,7 +119,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I should see button to the merge request' do
merge_request = MergeRequest.find_by(title: 'Feature')
- expect(page).to have_link "View open merge request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
+ expect(page).to have_link "View open merge request", href: project_merge_request_path(@project, merge_request)
end
step 'I see breadcrumb links' do
@@ -135,7 +135,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit a commit with an image that changed' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
+ visit project_commit_path(@project, sample_image_commit.id)
end
step 'The diff links to both the previous and current image' do
diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb
index 114de129d19..ebfa7a878bb 100644
--- a/features/steps/project/commits/revert.rb
+++ b/features/steps/project/commits/revert.rb
@@ -6,7 +6,7 @@ class Spinach::Features::RevertCommits < Spinach::FeatureSteps
include RepoHelpers
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I click on the revert button' do
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 2d43be5a386..4599e0d032a 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -4,11 +4,11 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedPaths
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I click on another commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id)
+ visit project_commit_path(@project, sample_commit.parent_id)
end
step 'I have user with primary email' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 28be9c6df5b..60fa232672e 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -7,12 +7,12 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
fill_in 'project_path', with: 'Empty'
page.within '#content-body' do
click_button "Create project"
- end
+ end
end
step 'I should see project page' do
expect(page).to have_content "Empty"
- expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
+ expect(current_path).to eq project_path(Project.last)
end
step 'I should see empty project instructions' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 8ad9d4a4741..b58d595c7c6 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project)
+ expect(current_path).to eq project_settings_repository_path(@project)
end
step 'I should see newly created deploy key' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 35df403a85f..dd4dff7f7a9 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -53,7 +53,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I visit the forks page of the "Shop" project' do
@project = Project.where(name: 'Shop').last
- visit namespace_project_forks_path(@project.namespace, @project)
+ visit project_forks_path(@project)
end
step 'I should see my fork on the list' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 2d9d3efd9d4..c6cabace25b 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see merge request "Merge Request On Forked Project"' do
expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq project_merge_request_path(@project, @merge_request)
expect(@merge_request.title).to eq "Merge Request On Forked Project"
expect(@merge_request.source_project).to eq @forked_project
expect(@merge_request.source_branch).to eq "fix"
@@ -77,7 +77,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_content "An Edited Forked Merge Request"
expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq project_merge_request_path(@project, @merge_request)
expect(@merge_request.source_project).to eq @forked_project
expect(@merge_request.source_branch).to eq "fix"
expect(@merge_request.target_branch).to eq "master"
@@ -97,7 +97,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
- expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq edit_project_merge_request_path(@project, @merge_request)
expect(page).to have_content "Edit merge request #{@merge_request.to_reference}"
expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project"
end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 176d04d721c..e78e25318a6 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -7,19 +7,19 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
When 'I visit project "Shop" graph page' do
- visit namespace_project_graph_path(project.namespace, project, "master")
+ visit project_graph_path(project, "master")
end
step 'I visit project "Shop" commits graph page' do
- visit commits_namespace_project_graph_path(project.namespace, project, "master")
+ visit commits_project_graph_path(project, "master")
end
step 'I visit project "Shop" languages graph page' do
- visit languages_namespace_project_graph_path(project.namespace, project, "master")
+ visit languages_project_graph_path(project, "master")
end
step 'I visit project "Shop" chart page' do
- visit charts_namespace_project_graph_path(project.namespace, project, "master")
+ visit charts_project_graph_path(project, "master")
end
step 'page should have languages graphs' do
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
step 'I visit project "Shop" CI graph page' do
- visit ci_namespace_project_graph_path(project.namespace, project, 'master')
+ visit ci_project_graph_path(project, 'master')
end
step 'page should have CI graphs' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2324edda975..bbd284b4633 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -5,7 +5,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
include Select2Helper
step 'I visit "Bugfix" issue page' do
- visit namespace_project_issue_path(@project.namespace, @project, @issue)
+ visit project_issue_path(@project, @issue)
end
step 'I click the thumbsup award Emoji' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index e4a559d8ff5..2deef9036d3 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -247,7 +247,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When 'I visit empty project page' do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I see empty project details with ssh clone info' do
@@ -259,12 +259,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When "I visit project \"Community\" issues page" do
project = Project.find_by(name: 'Community')
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
step 'I leave a comment with code block' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 2828e41f731..dac18c537ac 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedPaths
step 'I visit \'bug\' label edit page' do
- visit edit_namespace_project_label_path(project.namespace, project, bug_label)
+ visit edit_project_label_path(project, bug_label)
end
step 'I remove label \'bug\'' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 69f5d0f8410..810cd75591b 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -65,7 +65,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should not see "master" branch' do
- expect(find('.merge-request-info')).not_to have_content "master"
+ expect(find('.issuable-info')).not_to have_content "master"
end
step 'I should see "feature_conflict" branch' do
@@ -256,7 +256,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I switch to the merge request\'s comments tab' do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
step 'I click on the commit in the merge request' do
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 870dc862992..3c640e3512a 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -1,6 +1,5 @@
class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
include LoginHelpers
- include GitlabRoutingHelper
include WaitForRequests
step 'I am on the Merge Request detail page' do
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index 98d990f112f..25ccf5ab180 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -1,6 +1,5 @@
class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
include LoginHelpers
- include GitlabRoutingHelper
include WaitForRequests
step 'I click on the revert button' do
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 370e46265c7..ba98d861e7b 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -12,11 +12,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
Network::Graph.stub(max_count: 10)
@project = Project.find_by(name: "Shop")
- visit namespace_project_network_path(@project.namespace, @project, "master")
+ visit project_network_path(@project, "master")
end
step "I visit project network page on branch 'test'" do
- visit namespace_project_network_path(@project.namespace, @project, "'test'")
+ visit project_network_path(@project, "'test'")
end
step 'page should select "master" in select box' do
diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb
index 4e6830f738b..275fb4fc010 100644
--- a/features/steps/project/pages.rb
+++ b/features/steps/project/pages.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
end
step 'I visit the Project Pages' do
- visit namespace_project_pages_path(@project.namespace, @project)
+ visit project_pages_path(@project)
end
step 'I should see the usage of GitLab Pages' do
@@ -75,7 +75,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
end
step 'I visit add a new Pages Domain' do
- visit new_namespace_project_pages_domain_path(@project.namespace, @project)
+ visit new_project_pages_domain_path(@project)
end
step 'I fill the domain' do
diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb
index 5280a38ce81..47ee31786a6 100644
--- a/features/steps/project/project_group_links.rb
+++ b/features/steps/project/project_group_links.rb
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps
end
step 'I visit project group links page' do
- visit namespace_project_group_links_path(project.namespace, project)
+ visit project_group_links_path(project)
end
def project
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 92936f27c20..b2ceb8dd9a8 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -13,7 +13,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Community" page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I should see project "Community" home page' do
@@ -25,12 +25,12 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "CommunityDoesNotExist" page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_path(project.namespace, project) + 'DoesNotExist'
+ visit project_path(project) + 'DoesNotExist'
end
step 'I click on "Sign In"' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 6bac4df16f8..906a81b29b3 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
include SharedPaths
step 'I visit project "Shop" services page' do
- visit namespace_project_settings_integrations_path(@project.namespace, @project)
+ visit project_settings_integrations_path(@project)
end
step 'I should see list of available services' do
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index dd49701a3d9..b0407d3f07d 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -91,7 +91,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I visit snippet page "Snippet one"' do
- visit namespace_project_snippet_path(project.namespace, project, project_snippet)
+ visit project_snippet_path(project, project_snippet)
end
def project_snippet
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 9ed4f8ea1f9..621cae5d80d 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step "I don't have write access" do
@project = create(:project, :repository, name: "Other Project", path: "other-project")
@project.team << [@user, :reporter]
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I should see files from repository' do
@@ -19,7 +19,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see files from repository for "6d39438"' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ expect(current_path).to eq project_tree_path(@project, "6d39438")
expect(page).to have_content ".gitignore"
expect(page).to have_content "LICENSE"
end
@@ -92,10 +92,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
fill_in :branch_name, with: 'new_branch_name', visible: true
end
- step 'I fill the new file name with an illegal name' do
- fill_in :file_name, with: 'Spaces Not Allowed'
- end
-
step 'I fill the new file name with a new directory' do
fill_in :file_name, with: new_file_name_with_directory
end
@@ -240,16 +236,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I am redirected to the files URL' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, 'master')
+ expect(current_path).to eq project_tree_path(@project, 'master')
end
step 'I am redirected to the ".gitignore"' do
- expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
+ expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore'))
end
step 'I am redirected to the permalink URL' do
expect(current_path).to(
- eq(namespace_project_blob_path(@project.namespace, @project,
+ eq(project_blob_path(@project,
@project.repository.commit.sha +
'/.gitignore'))
)
@@ -257,26 +253,26 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I am redirected to the new file' do
expect(current_path).to eq(
- namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name))
+ project_blob_path(@project, 'master/' + new_file_name))
end
step 'I am redirected to the new file with directory' do
expect(current_path).to eq(
- namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory))
+ project_blob_path(@project, 'master/' + new_file_name_with_directory))
end
step 'I am redirected to the new merge request page' do
- expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
+ expect(current_path).to eq(project_new_merge_request_path(@project))
end
step "I am redirected to the fork's new merge request page" do
fork = @user.fork_of(@project)
- expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork))
end
step 'I am redirected to the root directory' do
expect(current_path).to eq(
- namespace_project_tree_path(@project.namespace, @project, 'master'))
+ project_tree_path(@project, 'master'))
end
step "I don't see the permalink link" do
@@ -327,11 +323,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step "I visit the 'test' tree" do
- visit namespace_project_tree_path(@project.namespace, @project, "'test'")
+ visit project_tree_path(@project, "'test'")
end
step "I visit the fix tree" do
- visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
+ visit project_tree_path(@project, "fix/.testdir")
end
step 'I see the commit data' do
@@ -346,7 +342,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "files/lfs/lfs_object.iso" file in repo' do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
- visit namespace_project_tree_path(@project.namespace, @project, "lfs")
+ visit project_tree_path(@project, "lfs")
click_link 'files'
click_link "lfs"
click_link "lfs_object.iso"
@@ -389,7 +385,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I visit the SVG file' do
- visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
+ visit project_blob_path(@project, 'new_branch_name/logo_sample.svg')
end
step 'I can see the new rendered SVG image' do
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index cf31e61437e..243a0f54f7f 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -14,7 +14,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see files from repository in markdown' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(current_path).to eq project_tree_path(@project, "markdown")
expect(page).to have_content "README.md"
expect(page).to have_content "CHANGELOG"
end
@@ -34,7 +34,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
@@ -44,7 +44,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct directory rendered' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
expect(page).to have_content "backup_restore.md"
expect(page).to have_content "maintenance.md"
end
@@ -54,7 +54,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct doc/api directory rendered' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
expect(page).to have_content "README.md"
expect(page).to have_content "users.md"
end
@@ -64,7 +64,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct maintenance file rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/raketasks/maintenance.md")
wait_for_requests
expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
@@ -98,7 +98,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see correct file rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
@@ -110,7 +110,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct document file' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
expect(page).to have_content "Get a list of users."
end
@@ -121,30 +121,30 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
# Markdown branch
When 'I visit markdown branch' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown")
+ visit project_tree_path(@project, "markdown")
wait_for_requests
end
When 'I visit markdown branch "README.md" blob' do
- visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ visit project_blob_path(@project, "markdown/README.md")
end
When 'I visit markdown branch "d" tree' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ visit project_tree_path(@project, "markdown/d")
end
When 'I visit markdown branch "d/README.md" blob' do
- visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ visit project_blob_path(@project, "markdown/d/README.md")
end
step 'I should see files from repository in markdown branch' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(current_path).to eq project_tree_path(@project, "markdown")
expect(page).to have_content "README.md"
expect(page).to have_content "CHANGELOG"
end
step 'I see correct file rendered in markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
@@ -152,19 +152,19 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered for markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
step 'I should see correct directory rendered for markdown branch' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
expect(page).to have_content "backup_restore.md"
expect(page).to have_content "maintenance.md"
end
step 'I should see the users document file in markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
expect(page).to have_content "Get a list of users."
end
@@ -172,54 +172,54 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'The link with text "empty" should have url "tree/markdown"' do
wait_for_requests
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
end
step 'The link with text "empty" should have url "blob/markdown/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
end
step 'The link with text "empty" should have url "tree/markdown/d"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
end
step 'The link with text "empty" should have '\
'url "blob/markdown/d/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
end
step 'The link with text "ID" should have url "tree/markdownID"' do
- find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "/ID" should have url "tree/markdownID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "README.mdID" '\
'should have url "blob/markdown/README.mdID"' do
- find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
step 'The link with text "d/README.mdID" should have '\
'url "blob/markdown/d/README.mdID"' do
- find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id'
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
wait_for_requests
- find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
# Wiki
step 'I go to wiki page' do
click_link "Wiki"
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
end
step 'I add various links to the wiki page' do
@@ -231,7 +231,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'Wiki page should have added links' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
expect(page).to have_content "test GitLab API doc Rake tasks"
end
@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see new wiki page named test' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
+ expect(current_path).to eq project_wiki_path(@project, "test")
page.within(:css, ".nav-text") do
expect(page).to have_content "Test"
@@ -261,8 +261,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
When 'I go back to wiki page home' do
- visit namespace_project_wiki_path(@project.namespace, @project, "home")
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ visit project_wiki_path(@project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
end
step 'I click on GitLab API doc link' do
@@ -270,7 +270,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Gitlab API document' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
+ expect(current_path).to eq project_wiki_path(@project, "api")
page.within(:css, ".nav-text") do
expect(page).to have_content "Create"
@@ -283,7 +283,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Rake tasks directory' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
+ expect(current_path).to eq project_wiki_path(@project, "raketasks")
page.within(:css, ".nav-text") do
expect(page).to have_content "Create"
@@ -292,8 +292,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I go directory which contains README file' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ visit project_tree_path(@project, "markdown/doc/api")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
end
step 'I click on a relative link in README' do
@@ -301,7 +301,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct markdown' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
wait_for_requests
expect(page).to have_content "List users"
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 517c257d892..a2f5d2e1515 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to the Edit Home Wiki page' do
- expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, :home)
+ expect(current_path).to eq project_wiki_path(project, :home)
end
step 'I create the Wiki Home page' do
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to that Wiki page' do
- visit namespace_project_wiki_path(project.namespace, project, @page)
+ visit project_wiki_path(project, @page)
end
step 'I click on the Edit button' do
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to that Wiki page' do
- expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, @page)
+ expect(current_path).to eq project_wiki_path(project, @page)
end
step 'That page has two revisions' do
@@ -95,7 +95,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to wiki page with images' do
- visit namespace_project_wiki_path(project.namespace, project, @wiki_page)
+ visit project_wiki_path(project, @wiki_page)
end
step 'I click on existing image link' do
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 624f1a7858b..3b4c98ec00d 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -27,11 +27,11 @@ module SharedBuilds
end
step 'I visit recent build details page' do
- visit namespace_project_job_path(@project.namespace, @project, @build)
+ visit project_job_path(@project, @build)
end
step 'I visit project builds page' do
- visit namespace_project_jobs_path(@project.namespace, @project)
+ visit project_jobs_path(@project)
end
step 'recent build has artifacts available' do
@@ -56,7 +56,7 @@ module SharedBuilds
end
step 'I access artifacts download page' do
- visit download_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ visit download_project_job_artifacts_path(@project, @build)
end
step 'I see details of a build' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 36fc315599e..2c59ec5bb06 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -232,7 +232,7 @@ module SharedDiffNote
end
def click_parallel_diff_line(code, line_type)
- find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover'
+ find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover'
find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
end
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 3d9cedf5c2d..7c842ba88fb 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -51,22 +51,22 @@ module SharedIssuable
step 'I visit issue page "Enterprise issue"' do
issue = Issue.find_by(title: 'Enterprise issue')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit merge request page "Enterprise fix"' do
mr = MergeRequest.find_by(title: 'Enterprise fix')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit issue page "Community issue"' do
issue = Issue.find_by(title: 'Community issue')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit issue page "Community fix"' do
mr = MergeRequest.find_by(title: 'Community fix')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
end
step 'I should not see any related merge requests' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 8a5b4112ffe..830263fd038 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -201,67 +201,67 @@ module SharedPaths
# ----------------------------------------
step "I visit my project's home page" do
- visit namespace_project_path(@project.namespace, @project)
+ visit project_path(@project)
end
step "I visit my project's settings page" do
- visit edit_namespace_project_path(@project.namespace, @project)
+ visit edit_project_path(@project)
end
step "I visit my project's files page" do
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I visit a binary file in the repo' do
- visit namespace_project_blob_path(@project.namespace, @project,
+ visit project_blob_path(@project,
File.join(root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+ visit project_commits_path(@project, root_ref, { limit: 5 })
end
step "I visit my project's commits page for a specific path" do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", { limit: 5 })
+ visit project_commits_path(@project, root_ref + "/app/models/project.rb", { limit: 5 })
end
step 'I visit my project\'s commits stats page' do
- visit stats_namespace_project_repository_path(@project.namespace, @project)
+ visit stats_project_repository_path(@project)
end
step "I visit my project's graph page" do
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- visit namespace_project_network_path(@project.namespace, @project, root_ref)
+ visit project_network_path(@project, root_ref)
end
step "I visit my project's issues page" do
- visit namespace_project_issues_path(@project.namespace, @project)
+ visit project_issues_path(@project)
end
step "I visit my project's merge requests page" do
- visit namespace_project_merge_requests_path(@project.namespace, @project)
+ visit project_merge_requests_path(@project)
end
step "I visit my project's members page" do
- visit namespace_project_project_members_path(@project.namespace, @project)
+ visit project_project_members_path(@project)
end
step "I visit my project's wiki page" do
- visit namespace_project_wiki_path(@project.namespace, @project, :home)
+ visit project_wiki_path(@project, :home)
end
step 'I visit project hooks page' do
- visit namespace_project_settings_integrations_path(@project.namespace, @project)
+ visit project_settings_integrations_path(@project)
end
step 'I visit project deploy keys page' do
- visit namespace_project_deploy_keys_path(@project.namespace, @project)
+ visit project_deploy_keys_path(@project)
end
step 'I visit project find file page' do
- visit namespace_project_find_file_path(@project.namespace, @project, root_ref)
+ visit project_find_file_path(@project, root_ref)
end
# ----------------------------------------
@@ -269,107 +269,107 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Shop" activity page' do
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
end
step 'I visit project "Forked Shop" merge requests page' do
- visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
+ visit project_merge_requests_path(@forked_project)
end
step 'I visit edit project "Shop" page' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
step 'I visit project branches page' do
- visit namespace_project_branches_path(@project.namespace, @project)
+ visit project_branches_path(@project)
end
step 'I visit project protected branches page' do
- visit namespace_project_protected_branches_path(@project.namespace, @project)
+ visit project_protected_branches_path(@project)
end
step 'I visit compare refs page' do
- visit namespace_project_compare_index_path(@project.namespace, @project)
+ visit project_compare_index_path(@project)
end
step 'I visit project commits page' do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+ visit project_commits_path(@project, root_ref, { limit: 5 })
end
step 'I visit project commits page for stable branch' do
- visit namespace_project_commits_path(@project.namespace, @project, 'stable', { limit: 5 })
+ visit project_commits_path(@project, 'stable', { limit: 5 })
end
step 'I visit project source page' do
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I visit blob file from repo' do
- visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path))
+ visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
end
step 'I visit ".gitignore" file in repo' do
- visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))
+ visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
end
step 'I am on the new file page' do
- expect(current_path).to eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+ expect(current_path).to eq(project_create_blob_path(@project, root_ref))
end
step 'I am on the ".gitignore" edit file page' do
expect(current_path).to eq(
- namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')))
+ project_edit_blob_path(@project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
- visit namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ visit project_tree_path(@project, "6d39438")
end
step 'I visit project source page for' \
' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do
- visit namespace_project_tree_path(@project.namespace, @project,
+ visit project_tree_path(@project,
'6d394385cf567f80a8fd85055db1ab4c5295806f')
end
step 'I visit project tags page' do
- visit namespace_project_tags_path(@project.namespace, @project)
+ visit project_tags_path(@project)
end
step 'I visit project commit page' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I visit project "Shop" issues page' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
step 'I visit issue page "Release 0.4"' do
issue = Issue.find_by(title: "Release 0.4")
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
end
step 'I visit project "Forum" labels page' do
project = Project.find_by(name: 'Forum')
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
end
step 'I visit project "Shop" new label page' do
project = Project.find_by(name: 'Shop')
- visit new_namespace_project_label_path(project.namespace, project)
+ visit new_project_label_path(project)
end
step 'I visit project "Forum" new label page' do
project = Project.find_by(name: 'Forum')
- visit new_namespace_project_label_path(project.namespace, project)
+ visit new_project_label_path(project)
end
step 'I visit merge request page "Bug NS-04"' do
@@ -394,28 +394,28 @@ module SharedPaths
step 'I visit merge request page "Bug CO-01"' do
mr = MergeRequest.find_by(title: "Bug CO-01")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
wait_for_requests
end
step 'I visit project "Shop" merge requests page' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
step 'I visit forked project "Shop" merge requests page' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
step 'I visit project "Shop" milestones page' do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
end
step 'I visit project "Shop" team page' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
end
step 'I visit project wiki page' do
- visit namespace_project_wiki_path(@project.namespace, @project, :home)
+ visit project_wiki_path(@project, :home)
end
# ----------------------------------------
@@ -424,22 +424,22 @@ module SharedPaths
step 'I visit project "Community" page' do
project = Project.find_by(name: "Community")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Community" source page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_tree_path(project.namespace, project, root_ref)
+ visit project_tree_path(project, root_ref)
end
step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: "Enterprise")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
# ----------------------------------------
@@ -448,7 +448,7 @@ module SharedPaths
step "I visit empty project page" do
project = Project.find_by(name: "Empty Public Project")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step "I should not see command line instructions" do
@@ -480,17 +480,13 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" snippets page' do
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
end
step 'I visit snippets page' do
visit explore_snippets_path
end
- step 'I visit new snippet page' do
- visit new_snippet_path
- end
-
def root_ref
@project.repository.root_ref
end
@@ -501,7 +497,7 @@ module SharedPaths
def merge_request_path(title)
mr = MergeRequest.find_by(title: title)
- namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ project_merge_request_path(mr.target_project, mr)
end
# ----------------------------------------
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index c4f1c57836f..729e2b8982c 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -61,12 +61,12 @@ module SharedProject
step 'I visit my empty project page' do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Shop" activity page' do
project = Project.find_by(name: 'Shop')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'project "Shop" has push event' do
@@ -101,7 +101,7 @@ module SharedProject
end
step 'I should see project settings' do
- expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
+ expect(current_path).to eq edit_project_path(@project)
expect(page).to have_content("Project name")
expect(page).to have_content("Sharing & Permissions")
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 0cb9229dbae..901f7f76ee9 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -32,6 +32,10 @@ module SharedProjectTab
ensure_active_main_tab('Wiki')
end
+ step 'the active main tab should be Members' do
+ ensure_active_main_tab('Members')
+ end
+
step 'the active main tab should be Settings' do
ensure_active_main_tab('Settings')
end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
deleted file mode 100644
index bb596c1620a..00000000000
--- a/features/steps/shared/snippet.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-module SharedSnippet
- include Spinach::DSL
-
- step 'I have public "Personal snippet one" snippet' do
- create(:personal_snippet,
- title: "Personal snippet one",
- content: "Test content",
- file_name: "snippet.rb",
- visibility_level: Snippet::PUBLIC,
- author: current_user)
- end
-
- step 'I have private "Personal snippet private" snippet' do
- create(:personal_snippet,
- title: "Personal snippet private",
- content: "Provate content",
- file_name: "private_snippet.rb",
- visibility_level: Snippet::PRIVATE,
- author: current_user)
- end
-
- step 'I have internal "Personal snippet internal" snippet' do
- create(:personal_snippet,
- title: "Personal snippet internal",
- content: "Provate content",
- file_name: "internal_snippet.rb",
- visibility_level: Snippet::INTERNAL,
- author: current_user)
- end
-
- step 'I have a public many lined snippet' do
- create(:personal_snippet,
- title: 'Many lined snippet',
- content: <<-END.gsub(/^\s+\|/, ''),
- |line one
- |line two
- |line three
- |line four
- |line five
- |line six
- |line seven
- |line eight
- |line nine
- |line ten
- |line eleven
- |line twelve
- |line thirteen
- |line fourteen
- END
- file_name: 'many_lined_snippet.rb',
- visibility_level: Snippet::PUBLIC,
- author: current_user)
- end
-
- step 'There is public "Personal snippet one" snippet' do
- create(:personal_snippet,
- title: "Personal snippet one",
- content: "Test content",
- file_name: "snippet.rb",
- visibility_level: Snippet::PUBLIC,
- author: create(:user))
- end
-end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
deleted file mode 100644
index a4fc77746ee..00000000000
--- a/features/steps/snippets/snippets.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-class Spinach::Features::Snippets < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedSnippet
- include WaitForRequests
-
- step 'I click link "Personal snippet one"' do
- click_link "Personal snippet one"
- end
-
- step 'I should not see "Personal snippet one" in snippets' do
- expect(page).not_to have_content "Personal snippet one"
- end
-
- step 'I click link "Edit"' do
- page.within ".detail-page-header" do
- first(:link, "Edit").click
- end
- end
-
- step 'I click link "Delete"' do
- first(:link, "Delete").click
- end
-
- step 'I submit new snippet "Personal snippet three"' do
- fill_in "personal_snippet_title", with: "Personal snippet three"
- fill_in "personal_snippet_file_name", with: "my_snippet.rb"
- page.within('.file-editor') do
- find('.ace_editor').native.send_keys 'Content of snippet three'
- end
- click_button "Create snippet"
- wait_for_requests
- end
-
- step 'I submit new internal snippet' do
- fill_in "personal_snippet_title", with: "Internal personal snippet one"
- fill_in "personal_snippet_file_name", with: "my_snippet.rb"
- choose 'personal_snippet_visibility_level_10'
-
- page.within('.file-editor') do
- find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
- end
-
- click_button "Create snippet"
- end
-
- step 'I should see snippet "Personal snippet three"' do
- expect(page).to have_content "Personal snippet three"
- expect(page).to have_content "Content of snippet three"
- end
-
- step 'I submit new title "Personal snippet new title"' do
- fill_in "personal_snippet_title", with: "Personal snippet new title"
- click_button "Save"
- end
-
- step 'I should see "Personal snippet new title"' do
- expect(page).to have_content "Personal snippet new title"
- end
-
- step 'I uncheck "Private" checkbox' do
- choose "Internal"
- click_button "Save"
- end
-
- step 'I should see "Personal snippet one" public' do
- expect(page).to have_no_xpath("//i[@class='public-snippet']")
- end
-
- step 'I visit snippet page "Personal snippet one"' do
- visit snippet_path(snippet)
- end
-
- step 'I visit snippet page "Internal personal snippet one"' do
- visit snippet_path(internal_snippet)
- end
-
- def snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
- end
-
- def internal_snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
- end
-end
diff --git a/features/support/env.rb b/features/support/env.rb
index 1690465d9b2..608d988755c 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -28,6 +28,7 @@ Spinach.hooks.before_run do
TestEnv.disable_pre_receive
include FactoryGirl::Syntax::Methods
+ include GitlabRoutingHelper
end
Spinach.hooks.after_scenario do |scenario_data, step_definitions|
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d767af36e8e..efcf0976a81 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,6 +2,8 @@ module API
class API < Grape::API
include APIGuard
+ allow_access_with_scope :api
+
version %w(v3 v4), using: :path
version 'v3', using: :path do
@@ -44,7 +46,6 @@ module API
mount ::API::V3::Variables
end
- before { allow_access_with_scope :api }
before { header['X-Frame-Options'] = 'SAMEORIGIN' }
before { Gitlab::I18n.locale = current_user&.preferred_language }
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9fcf04efa38..0d2d71e336a 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -23,6 +23,23 @@ module API
install_error_responders(base)
end
+ class_methods do
+ # Set the authorization scope(s) allowed for an API endpoint.
+ #
+ # A call to this method maps the given scope(s) to the current API
+ # endpoint class. If this method is called multiple times on the same class,
+ # the scopes are all aggregated.
+ def allow_access_with_scope(scopes, options = {})
+ Array(scopes).each do |scope|
+ allowed_scopes << Scope.new(scope, options)
+ end
+ end
+
+ def allowed_scopes
+ @scopes ||= []
+ end
+ end
+
# Helper Methods for Grape Endpoint
module HelperMethods
# Invokes the doorkeeper guard.
@@ -47,7 +64,7 @@ module API
access_token = find_access_token
return nil unless access_token
- case AccessTokenValidationService.new(access_token).validate(scopes: scopes)
+ case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
@@ -74,18 +91,6 @@ module API
@current_user
end
- # Set the authorization scope(s) allowed for the current request.
- #
- # Note: A call to this method adds to any previous scopes in place. This is done because
- # `Grape` callbacks run from the outside-in: the top-level callback (API::API) runs first, then
- # the next-level callback (API::API::Users, for example) runs. All these scopes are valid for the
- # given endpoint (GET `/api/users` is accessible by the `api` and `read_user` scopes), and so they
- # need to be stored.
- def allow_access_with_scope(*scopes)
- @scopes ||= []
- @scopes.concat(scopes.map(&:to_s))
- end
-
private
def find_user_by_authentication_token(token_string)
@@ -96,7 +101,7 @@ module API
access_token = PersonalAccessToken.active.find_by_token(token_string)
return unless access_token
- if AccessTokenValidationService.new(access_token).include_any_scope?(scopes)
+ if AccessTokenValidationService.new(access_token, request: request).include_any_scope?(scopes)
User.find(access_token.user_id)
end
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index c6fc17cc391..bcb842b9211 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -67,7 +67,7 @@ module API
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
- commit_detail = user_project.repository.commits(result[:result], limit: 1).first
+ commit_detail = user_project.repository.commit(result[:result])
present commit_detail, with: Entities::RepoCommitDetail
else
render_api_error!(result[:message], 400)
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index aa91451c9f4..945f2821d72 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -112,6 +112,7 @@ module API
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
+ expose :ci_config_path
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
@@ -254,7 +255,7 @@ module API
class ProjectEntity < Grape::Entity
expose :id, :iid
- expose(:project_id) { |entity| entity.project.id }
+ expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
expose :state, :created_at, :updated_at
end
@@ -266,7 +267,12 @@ module API
expose :deleted_file?, as: :deleted_file
end
- class Milestone < ProjectEntity
+ class Milestone < Grape::Entity
+ expose :id, :iid
+ expose(:project_id) { |entity| entity&.project_id }
+ expose(:group_id) { |entity| entity&.group_id }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
end
@@ -310,10 +316,26 @@ module API
class MergeRequestBasic < ProjectEntity
expose :target_branch, :source_branch
- expose :upvotes, :downvotes
+ expose :upvotes do |merge_request, options|
+ if options[:issuable_metadata]
+ options[:issuable_metadata][merge_request.id].upvotes
+ else
+ merge_request.upvotes
+ end
+ end
+ expose :downvotes do |merge_request, options|
+ if options[:issuable_metadata]
+ options[:issuable_metadata][merge_request.id].downvotes
+ else
+ merge_request.downvotes
+ end
+ end
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
- expose :label_names, as: :labels
+ expose :labels do |merge_request, options|
+ # Avoids an N+1 query since labels are preloaded
+ merge_request.labels.map(&:title).sort
+ end
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
@@ -434,7 +456,7 @@ module API
target_url = "namespace_project_#{target_type}_url"
target_anchor = "note_#{todo.note_id}" if todo.note_id?
- Gitlab::Application.routes.url_helpers.public_send(target_url,
+ Gitlab::Routing.url_helpers.public_send(target_url,
todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
end
@@ -444,7 +466,15 @@ module API
end
class Namespace < Grape::Entity
- expose :id, :name, :path, :kind, :full_path
+ expose :id, :name, :path, :kind, :full_path, :parent_id
+
+ expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
+ namespace.users_with_descendants.count
+ end
+
+ def expose_members_count_with_descendants?(namespace, opts)
+ namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
+ end
end
class MemberAccess < Grape::Entity
@@ -494,12 +524,20 @@ module API
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
- project.project_members.find_by(user_id: options[:current_user].id)
+ if options.key?(:project_members)
+ (options[:project_members] || []).find { |member| member.source_id == project.id }
+ else
+ project.project_members.find_by(user_id: options[:current_user].id)
+ end
end
expose :group_access, using: Entities::GroupAccess do |project, options|
if project.group
- project.group.group_members.find_by(user_id: options[:current_user].id)
+ if options.key?(:group_members)
+ (options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
+ else
+ project.group.group_members.find_by(user_id: options[:current_user].id)
+ end
end
end
end
@@ -823,7 +861,7 @@ module API
end
class Cache < Grape::Entity
- expose :key, :untracked, :paths
+ expose :key, :untracked, :paths, :policy
end
class Credentials < Grape::Entity
@@ -866,5 +904,11 @@ module API
expose :dependencies, using: Dependency
end
end
+
+ class UserAgentDetail < Grape::Entity
+ expose :user_agent
+ expose :ip_address
+ expose :submitted, as: :akismet_submitted
+ end
end
end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index cff0ba2ddff..9385c6ca174 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -2,6 +2,27 @@ module API
class Features < Grape::API
before { authenticated_as_admin! }
+ helpers do
+ def gate_value(params)
+ case params[:value]
+ when 'true'
+ true
+ when '0', 'false'
+ false
+ else
+ params[:value].to_i
+ end
+ end
+
+ def gate_targets(params)
+ targets = []
+ targets << Feature.group(params[:feature_group]) if params[:feature_group]
+ targets << User.find_by_username(params[:user]) if params[:user]
+
+ targets
+ end
+ end
+
resource :features do
desc 'Get a list of all features' do
success Entities::Feature
@@ -17,16 +38,29 @@ module API
end
params do
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
+ optional :feature_group, type: String, desc: 'A Feature group name'
+ optional :user, type: String, desc: 'A GitLab username'
end
post ':name' do
feature = Feature.get(params[:name])
+ targets = gate_targets(params)
+ value = gate_value(params)
- if %w(0 false).include?(params[:value])
- feature.disable
- elsif params[:value] == 'true'
- feature.enable
+ case value
+ when true
+ if targets.present?
+ targets.each { |target| feature.enable(target) }
+ else
+ feature.enable
+ end
+ when false
+ if targets.present?
+ targets.each { |target| feature.disable(target) }
+ else
+ feature.disable
+ end
else
- feature.enable_percentage_of_time(params[:value].to_i)
+ feature.enable_percentage_of_time(value)
end
present feature, with: Entities::Feature, current_user: current_user
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2c73a6fdc4e..0f4791841d2 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -268,6 +268,7 @@ module API
finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
finder_params[:archived] = params[:archived]
finder_params[:search] = params[:search] if params[:search]
+ finder_params[:user] = params.delete(:user) if params[:user]
finder_params
end
@@ -313,7 +314,7 @@ module API
def present_artifacts!(artifacts_file)
return not_found! unless artifacts_file.exists?
-
+
if artifacts_file.file_storage?
present_file!(artifacts_file.path, artifacts_file.filename)
else
@@ -342,8 +343,8 @@ module API
def initial_current_user
return @initial_current_user if defined?(@initial_current_user)
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
- @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
- @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
+ @initial_current_user ||= find_user_by_private_token(scopes: scopes_registered_for_endpoint)
+ @initial_current_user ||= doorkeeper_guard(scopes: scopes_registered_for_endpoint)
@initial_current_user ||= find_user_from_warden
unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
@@ -407,5 +408,22 @@ module API
exception.status == 500
end
+
+ # An array of scopes that were registered (using `allow_access_with_scope`)
+ # for the current endpoint class. It also returns scopes registered on
+ # `API::API`, since these are meant to apply to all API routes.
+ def scopes_registered_for_endpoint
+ @scopes_registered_for_endpoint ||=
+ begin
+ endpoint_classes = [options[:for].presence, ::API::API].compact
+ endpoint_classes.reduce([]) do |memo, endpoint|
+ if endpoint.respond_to?(:allowed_scopes)
+ memo.concat(endpoint.allowed_scopes)
+ else
+ memo
+ end
+ end
+ end
+ end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 5e9cf5e68b1..ecb79317093 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -1,6 +1,11 @@
module API
module Helpers
module InternalHelpers
+ SSH_GITALY_FEATURES = {
+ 'git-receive-pack' => :ssh_receive_pack,
+ 'git-upload-pack' => :ssh_upload_pack
+ }.freeze
+
def wiki?
set_project unless defined?(@wiki)
@wiki
@@ -10,7 +15,7 @@ module API
set_project unless defined?(@project)
@project
end
-
+
def redirected_path
@redirected_path
end
@@ -54,15 +59,33 @@ module API
Gitlab::GlRepository.gl_repository(project, wiki?)
end
- # Return the repository full path so that gitlab-shell has it when
- # handling ssh commands
- def repository_path
+ # Return the repository depending on whether we want the wiki or the
+ # regular repository
+ def repository
if wiki?
- project.wiki.repository.path_to_repo
+ project.wiki.repository
else
- project.repository.path_to_repo
+ project.repository
end
end
+
+ # Return the repository full path so that gitlab-shell has it when
+ # handling ssh commands
+ def repository_path
+ repository.path_to_repo
+ end
+
+ # Return the Gitaly Address if it is enabled
+ def gitaly_payload(action)
+ feature = SSH_GITALY_FEATURES[action]
+ return unless feature && Gitlab::GitalyClient.feature_enabled?(feature)
+
+ {
+ repository: repository.gitaly_repository,
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ }
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 1369b021ea4..f8645e364ce 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -46,7 +46,8 @@ module API
yield if block_given?
- forbidden!('Project has been deleted!') unless job.project
+ project = job.project
+ forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
forbidden!('Job has been erased!') if job.erased?
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index f1c79970ba4..ef2c08e902c 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -47,7 +47,8 @@ module API
{
status: true,
gl_repository: gl_repository,
- repository_path: repository_path
+ repository_path: repository_path,
+ gitaly: gitaly_payload(params[:action])
}
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 09dca0dff8b..64be08094ed 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -241,6 +241,22 @@ module API
present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
+
+ desc 'Get the user agent details for an issue' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
+ end
+ get ":id/issues/:issue_iid/user_agent_detail" do
+ authenticated_as_admin!
+
+ issue = find_project_issue(params[:issue_iid])
+
+ return not_found!('UserAgentDetail') unless issue.user_agent_detail
+
+ present issue.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 1118fc7465b..4ad1eef4ff1 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -10,6 +10,8 @@ module API
resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
+ helpers ::Gitlab::IssuableMetadata
+
helpers do
def handle_merge_request_errors!(errors)
if errors[:project_access].any?
@@ -41,7 +43,8 @@ module API
args[:milestone_title] = args.delete(:milestone)
args[:label_name] = args.delete(:labels)
- merge_requests = MergeRequestsFinder.new(current_user, args).execute.inc_notes_with_associations
+ merge_requests = MergeRequestsFinder.new(current_user, args).execute
+ .preload(:notes, :target_project, :author, :assignee, :milestone, :merge_request_diff, :labels)
merge_requests.reorder(args[:order_by] => args[:sort])
end
@@ -80,8 +83,9 @@ module API
authorize! :read_merge_request, user_project
merge_requests = find_merge_requests(project_id: user_project.id)
+ issuable_metadata = issuable_meta_data(merge_requests, 'MergeRequest')
- present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
+ present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project, issuable_metadata: issuable_metadata
end
desc 'Create a merge request' do
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 30761cb9b55..f1eaff6b0eb 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -17,7 +17,7 @@ module API
namespaces = namespaces.search(params[:search]) if params[:search].present?
- present paginate(namespaces), with: Entities::Namespace
+ present paginate(namespaces), with: Entities::Namespace, current_user: current_user
end
end
end
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index 93d89209934..dbeaf9e17ef 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -74,9 +74,10 @@ module API
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :update_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
present pipeline_schedule, with: Entities::PipelineScheduleDetails
@@ -92,9 +93,10 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :update_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.own!(current_user)
present pipeline_schedule, with: Entities::PipelineScheduleDetails
@@ -110,9 +112,10 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :admin_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :admin_pipeline_schedule, pipeline_schedule
status :accepted
present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 64efe82a937..3320eadff0d 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -131,6 +131,22 @@ module API
content_type 'text/plain'
present snippet.content
end
+
+ desc 'Get the user agent details for a project snippet' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
+ end
+ get ":id/snippets/:snippet_id/user_agent_detail" do
+ authenticated_as_admin!
+
+ snippet = Snippet.find_by!(id: params[:id])
+
+ return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+
+ present snippet.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index c5df45b7902..c459257158d 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,3 +1,5 @@
+require_dependency 'declarative_policy'
+
module API
# Projects API
class Projects < Grape::API
@@ -8,6 +10,7 @@ module API
helpers do
params :optional_params_ce do
optional :description, type: String, desc: 'The description of the project'
+ optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
@@ -33,61 +36,86 @@ module API
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
- end
- resource :projects do
- helpers do
- params :collection_params do
- use :sort_params
- use :filter_params
- use :pagination
-
- optional :simple, type: Boolean, default: false,
- desc: 'Return only the ID, URL, name, and path of each project'
- end
+ params :collection_params do
+ use :sort_params
+ use :filter_params
+ use :pagination
- params :sort_params do
- optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
- default: 'created_at', desc: 'Return projects ordered by field'
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return projects sorted in ascending and descending order'
- end
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+ end
- params :filter_params do
- optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
- optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
- desc: 'Limit by visibility'
- optional :search, type: String, desc: 'Return list of projects matching the search criteria'
- optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
- optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
- optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
- optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
- optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
- end
+ params :sort_params do
+ optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+ default: 'created_at', desc: 'Return projects ordered by field'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return projects sorted in ascending and descending order'
+ end
- params :create_params do
- optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
- optional :import_url, type: String, desc: 'URL from which the project is imported'
- end
+ params :filter_params do
+ optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
+ desc: 'Limit by visibility'
+ optional :search, type: String, desc: 'Return list of projects matching the search criteria'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+ optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
+ optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
+ optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
+ end
+
+ params :create_params do
+ optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
+ optional :import_url, type: String, desc: 'URL from which the project is imported'
+ end
+
+ def present_projects(options = {})
+ projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ projects = projects.with_statistics if params[:statistics]
+ projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
- def present_projects(options = {})
- projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
- projects = reorder_projects(projects)
- projects = projects.with_statistics if params[:statistics]
- projects = projects.with_issues_enabled if params[:with_issues_enabled]
- projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
-
- options = options.reverse_merge(
- with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
- statistics: params[:statistics],
- current_user: current_user
- )
- options[:with] = Entities::BasicProjectDetails if params[:simple]
-
- present paginate(projects), options
+ if current_user
+ projects = projects.includes(:route, :taggings, namespace: :route)
+ project_members = current_user.project_members
+ group_members = current_user.group_members
end
+
+ options = options.reverse_merge(
+ with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
+ statistics: params[:statistics],
+ project_members: project_members,
+ group_members: group_members,
+ current_user: current_user
+ )
+ options[:with] = Entities::BasicProjectDetails if params[:simple]
+
+ present paginate(projects), options
end
+ end
+ resource :users, requirements: { user_id: %r{[^/]+} } do
+ desc 'Get a user projects' do
+ success Entities::BasicProjectDetails
+ end
+ params do
+ requires :user_id, type: String, desc: 'The ID or username of the user'
+ use :collection_params
+ use :statistics_params
+ end
+ get ":user_id/projects" do
+ user = find_user(params[:user_id])
+ not_found!('User') unless user
+
+ params[:user] = user
+
+ present_projects
+ end
+ end
+
+ resource :projects do
desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails
end
@@ -396,7 +424,7 @@ module API
use :pagination
end
get ':id/users' do
- users = user_project.team.users
+ users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
present paginate(users), with: Entities::UserBasic
diff --git a/lib/api/scope.rb b/lib/api/scope.rb
new file mode 100644
index 00000000000..d5165b2e482
--- /dev/null
+++ b/lib/api/scope.rb
@@ -0,0 +1,23 @@
+# Encapsulate a scope used for authorization, such as `api`, or `read_user`
+module API
+ class Scope
+ attr_reader :name, :if
+
+ def initialize(name, options = {})
+ @name = name.to_sym
+ @if = options[:if]
+ end
+
+ # Are the `scopes` passed in sufficient to adequately authorize the passed
+ # request for the scope represented by the current instance of this class?
+ def sufficient?(scopes, request)
+ scopes.include?(self.name) && verify_if_condition(request)
+ end
+
+ private
+
+ def verify_if_condition(request)
+ self.if.nil? || self.if.call(request)
+ end
+ end
+end
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index c630c24c339..fd634037a77 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -140,6 +140,22 @@ module API
content_type 'text/plain'
present snippet.content
end
+
+ desc 'Get the user agent details for a snippet' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ end
+ get ":id/user_agent_detail" do
+ authenticated_as_admin!
+
+ snippet = Snippet.find_by!(id: params[:id])
+
+ return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+
+ present snippet.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index f9555842daf..c469751c31c 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,13 +1,15 @@
module API
class Users < Grape::API
include PaginationParams
+ include APIGuard
- before do
- allow_access_with_scope :read_user if request.get?
- authenticate!
- end
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+ before do
+ authenticate_non_get!
+ end
+
helpers do
def find_user(params)
id = params[:user_id] || params[:id]
@@ -46,20 +48,33 @@ module API
optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
+ optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider
use :pagination
end
get do
- unless can?(current_user, :read_users_list)
- render_api_error!("Not authorized.", 403)
- end
-
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
+ unless current_user&.admin?
+ params.except!(:created_after, :created_before)
+ end
+
users = UsersFinder.new(current_user, params).execute
- entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic
+ authorized = can?(current_user, :read_users_list)
+
+ # When `current_user` is not present, require that the `username`
+ # parameter is passed, to prevent an unauthenticated user from accessing
+ # a list of all the users on the GitLab instance. `UsersFinder` performs
+ # an exact match on the `username` parameter, so we are guaranteed to
+ # get either 0 or 1 `users` here.
+ authorized &&= params[:username].present? if current_user.blank?
+
+ forbidden!("Not authorized to access /api/v4/users") unless authorized
+
+ entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity
end
@@ -398,6 +413,10 @@ module API
end
resource :user do
+ before do
+ authenticate!
+ end
+
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
index 37020019e07..cf106f2552d 100644
--- a/lib/api/v3/users.rb
+++ b/lib/api/v3/users.rb
@@ -2,9 +2,11 @@ module API
module V3
class Users < Grape::API
include PaginationParams
+ include APIGuard
+
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
before do
- allow_access_with_scope :read_user if request.get?
authenticate!
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 10374995497..7fa528fb2d3 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -45,7 +45,9 @@ module API
optional :protected, type: String, desc: 'Whether the variable is protected'
end
post ':id/variables' do
- variable = user_project.variables.create(declared_params(include_missing: false))
+ variable_params = declared_params(include_missing: false)
+
+ variable = user_project.variables.create(variable_params)
if variable.valid?
present variable, with: Entities::Variable
@@ -67,7 +69,9 @@ module API
return not_found!('Variable') unless variable
- if variable.update(declared_params(include_missing: false).except(:key))
+ variable_params = declared_params(include_missing: false).except(:key)
+
+ if variable.update(variable_params)
present variable, with: Entities::Variable
else
render_validation_error!(variable)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 8bc2dd18bda..7a262dd025c 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -216,12 +216,7 @@ module Banzai
@references_per_project ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
- regex =
- if uses_reference_pattern?
- Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
- else
- object_class.link_reference_pattern
- end
+ regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
nodes.each do |node|
node.to_html.scan(regex) do
@@ -323,14 +318,6 @@ module Banzai
value
end
end
-
- # There might be special cases like filters
- # that should ignore reference pattern
- # eg: IssueReferenceFilter when using a external issues tracker
- # In those cases this method should be overridden on the filter subclass
- def uses_reference_pattern?
- true
- end
end
end
end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index eaacb9591b1..21bcb1c5ca8 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -30,7 +30,7 @@ module Banzai
def url_for_object(range, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_compare_url(project.namespace, project,
+ h.project_compare_url(project,
range.to_param.merge(only_path: context[:only_path]))
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 69c06117eda..714e0319025 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -24,7 +24,7 @@ module Banzai
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_commit_url(project.namespace, project, commit,
+ h.project_commit_url(project, commit,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index dce4de3ceaf..53a229256a5 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -3,6 +3,8 @@ module Banzai
# HTML filter that replaces external issue tracker references with links.
# References are ignored if the project doesn't use an external issue
# tracker.
+ #
+ # This filter does not support cross-project references.
class ExternalIssueReferenceFilter < ReferenceFilter
self.reference_type = :external_issue
@@ -87,7 +89,7 @@ module Banzai
end
def issue_reference_pattern
- external_issues_cached(:issue_reference_pattern)
+ external_issues_cached(:external_issue_reference_pattern)
end
private
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 044d18ff824..ba1a5ac84b3 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -15,10 +15,6 @@ module Banzai
Issue
end
- def uses_reference_pattern?
- context[:project].default_issues_tracker?
- end
-
def find_object(project, iid)
issues_per_project[project][iid]
end
@@ -38,13 +34,7 @@ module Banzai
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
-
- issues =
- if project.default_issues_tracker?
- project.issues.where(iid: issue_ids.to_a)
- else
- issue_ids.map { |id| ExternalIssue.new(id, project) }
- end
+ issues = project.issues.where(iid: issue_ids.to_a)
issues.each do |issue|
hash[project][issue.iid.to_i] = issue
@@ -55,26 +45,6 @@ module Banzai
end
end
- def object_link_title(object)
- if object.is_a?(ExternalIssue)
- "Issue in #{object.project.external_issue_tracker.title}"
- else
- super
- end
- end
-
- def data_attributes_for(text, project, object, link: false)
- if object.is_a?(ExternalIssue)
- data_attribute(
- project: project.id,
- external_issue: object.id,
- reference_type: ExternalIssueReferenceFilter.reference_type
- )
- else
- super
- end
- end
-
def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index a605dea149e..5364984c9d3 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -61,8 +61,7 @@ module Banzai
def url_for_object(label, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
+ h.project_issues_url(project, label_name: label.name, only_path: context[:only_path])
end
def object_link_text(object, matches)
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 3888acf935e..0eab865ac04 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_merge_request_url(project.namespace, project, mr,
+ h.project_merge_request_url(project, mr,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index f12014e191f..45c033d32a8 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -49,7 +49,7 @@ module Banzai
def url_for_object(milestone, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_milestone_url(project.namespace, project, milestone,
+ h.project_milestone_url(project, milestone,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 6640168bfa2..a6f8650ed3d 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -30,6 +30,8 @@ module Banzai
attributes = attributes.reject { |_, v| v.nil? }
attributes[:reference_type] ||= self.class.reference_type
+ attributes[:container] ||= 'body'
+ attributes[:placement] ||= 'bottom'
attributes.delete(:original) if context[:no_original_data]
attributes.map do |key, value|
%Q(data-#{key.to_s.dasherize}="#{escape_once(value)}")
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index 212a0bbf2a0..134a192c22b 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
def url_for_object(snippet, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_snippet_url(project.namespace, project, snippet,
+ h.project_snippet_url(project, snippet,
only_path: context[:only_path])
end
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index a798927823f..f3356d6c51e 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -107,7 +107,7 @@ module Banzai
if author && !project.team.member?(author)
link_content
else
- url = urls.namespace_project_url(project.namespace, project,
+ url = urls.project_url(project,
only_path: context[:only_path])
data = data_attribute(project: project.id, author: author.try(:id))
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 9fd4bd68d43..a65bbe23958 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -4,9 +4,6 @@ module Banzai
self.reference_type = :issue
def nodes_visible_to_user(user, nodes)
- # It is not possible to check access rights for external issue trackers
- return nodes if project && project.external_issue_tracker
-
issues = issues_for_nodes(nodes)
readable_issues = Ability
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 5109dc9670f..a40b6ab6c9f 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -28,7 +28,8 @@ module Ci
yield if block_given?
- forbidden!('Project has been deleted!') unless build.project
+ project = build.project
+ forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
forbidden!('Build has been erased!') if build.erased?
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
new file mode 100644
index 00000000000..d9959bc1aff
--- /dev/null
+++ b/lib/declarative_policy.rb
@@ -0,0 +1,58 @@
+require_dependency 'declarative_policy/cache'
+require_dependency 'declarative_policy/condition'
+require_dependency 'declarative_policy/dsl'
+require_dependency 'declarative_policy/preferred_scope'
+require_dependency 'declarative_policy/rule'
+require_dependency 'declarative_policy/runner'
+require_dependency 'declarative_policy/step'
+
+require_dependency 'declarative_policy/base'
+
+module DeclarativePolicy
+ class << self
+ def policy_for(user, subject, opts = {})
+ cache = opts[:cache] || {}
+ key = Cache.policy_key(user, subject)
+
+ cache[key] ||= class_for(subject).new(user, subject, opts)
+ end
+
+ def class_for(subject)
+ return GlobalPolicy if subject == :global
+ return NilPolicy if subject.nil?
+
+ subject = find_delegate(subject)
+
+ subject.class.ancestors.each do |klass|
+ next unless klass.name
+
+ begin
+ policy_class = "#{klass.name}Policy".constantize
+
+ # NOTE: the < operator here tests whether policy_class
+ # inherits from Base. We can't use #is_a? because that
+ # tests for *instances*, not *subclasses*.
+ return policy_class if policy_class < Base
+ rescue NameError
+ nil
+ end
+ end
+
+ raise "no policy for #{subject.class.name}"
+ end
+
+ private
+
+ def find_delegate(subject)
+ seen = Set.new
+
+ while subject.respond_to?(:declarative_policy_delegate)
+ raise ArgumentError, "circular delegations" if seen.include?(subject.object_id)
+ seen << subject.object_id
+ subject = subject.declarative_policy_delegate
+ end
+
+ subject
+ end
+ end
+end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
new file mode 100644
index 00000000000..df94cafb6a1
--- /dev/null
+++ b/lib/declarative_policy/base.rb
@@ -0,0 +1,329 @@
+module DeclarativePolicy
+ class Base
+ # A map of ability => list of rules together with :enable
+ # or :prevent actions. Used to look up which rules apply to
+ # a given ability. See Base.ability_map
+ class AbilityMap
+ attr_reader :map
+ def initialize(map = {})
+ @map = map
+ end
+
+ # This merge behavior is different than regular hashes - if both
+ # share a key, the values at that key are concatenated, rather than
+ # overridden.
+ def merge(other)
+ conflict_proc = proc { |key, my_val, other_val| my_val + other_val }
+ AbilityMap.new(@map.merge(other.map, &conflict_proc))
+ end
+
+ def actions(key)
+ @map[key] ||= []
+ end
+
+ def enable(key, rule)
+ actions(key) << [:enable, rule]
+ end
+
+ def prevent(key, rule)
+ actions(key) << [:prevent, rule]
+ end
+ end
+
+ class << self
+ # The `own_ability_map` vs `ability_map` distinction is used so that
+ # the data structure is properly inherited - with subclasses recursively
+ # merging their parent class.
+ #
+ # This pattern is also used for conditions, global_actions, and delegations.
+ def ability_map
+ if self == Base
+ own_ability_map
+ else
+ superclass.ability_map.merge(own_ability_map)
+ end
+ end
+
+ def own_ability_map
+ @own_ability_map ||= AbilityMap.new
+ end
+
+ # an inheritable map of conditions, by name
+ def conditions
+ if self == Base
+ own_conditions
+ else
+ superclass.conditions.merge(own_conditions)
+ end
+ end
+
+ def own_conditions
+ @own_conditions ||= {}
+ end
+
+ # a list of global actions, generated by `prevent_all`. these aren't
+ # stored in `ability_map` because they aren't indexed by a particular
+ # ability.
+ def global_actions
+ if self == Base
+ own_global_actions
+ else
+ superclass.global_actions + own_global_actions
+ end
+ end
+
+ def own_global_actions
+ @own_global_actions ||= []
+ end
+
+ # an inheritable map of delegations, indexed by name (which may be
+ # autogenerated)
+ def delegations
+ if self == Base
+ own_delegations
+ else
+ superclass.delegations.merge(own_delegations)
+ end
+ end
+
+ def own_delegations
+ @own_delegations ||= {}
+ end
+
+ # all the [rule, action] pairs that apply to a particular ability.
+ # we combine the specific ones looked up in ability_map with the global
+ # ones.
+ def configuration_for(ability)
+ ability_map.actions(ability) + global_actions
+ end
+
+ ### declaration methods ###
+
+ def delegate(name = nil, &delegation_block)
+ if name.nil?
+ @delegate_name_counter ||= 0
+ @delegate_name_counter += 1
+ name = :"anonymous_#{@delegate_name_counter}"
+ end
+
+ name = name.to_sym
+
+ if delegation_block.nil?
+ delegation_block = proc { @subject.__send__(name) }
+ end
+
+ own_delegations[name] = delegation_block
+ end
+
+ # Declares a rule, constructed using RuleDsl, and returns
+ # a PolicyDsl which is used for registering the rule with
+ # this class. PolicyDsl will call back into Base.enable_when,
+ # Base.prevent_when, and Base.prevent_all_when.
+ def rule(&b)
+ rule = RuleDsl.new(self).instance_eval(&b)
+ PolicyDsl.new(self, rule)
+ end
+
+ # A hash in which to store calls to `desc` and `with_scope`, etc.
+ def last_options
+ @last_options ||= {}.with_indifferent_access
+ end
+
+ # retrieve and zero out the previously set options (used in .condition)
+ def last_options!
+ last_options.tap { @last_options = nil }
+ end
+
+ # Declare a description for the following condition. Currently unused,
+ # but opens the potential for explaining to users why they were or were
+ # not able to do something.
+ def desc(description)
+ last_options[:description] = description
+ end
+
+ def with_options(opts = {})
+ last_options.merge!(opts)
+ end
+
+ def with_scope(scope)
+ with_options scope: scope
+ end
+
+ def with_score(score)
+ with_options score: score
+ end
+
+ # Declares a condition. It gets stored in `own_conditions`, and generates
+ # a query method based on the condition's name.
+ def condition(name, opts = {}, &value)
+ name = name.to_sym
+
+ opts = last_options!.merge(opts)
+ opts[:context_key] ||= self.name
+
+ condition = Condition.new(name, opts, &value)
+
+ self.own_conditions[name] = condition
+
+ define_method(:"#{name}?") { condition(name).pass? }
+ end
+
+ # These next three methods are mainly called from PolicyDsl,
+ # and are responsible for "inverting" the relationship between
+ # an ability and a rule. We store in `ability_map` a map of
+ # abilities to rules that affect them, together with a
+ # symbol indicating :prevent or :enable.
+ def enable_when(abilities, rule)
+ abilities.each { |a| own_ability_map.enable(a, rule) }
+ end
+
+ def prevent_when(abilities, rule)
+ abilities.each { |a| own_ability_map.prevent(a, rule) }
+ end
+
+ # we store global prevents (from `prevent_all`) separately,
+ # so that they can be combined into every decision made.
+ def prevent_all_when(rule)
+ own_global_actions << [:prevent, rule]
+ end
+ end
+
+ # A policy object contains a specific user and subject on which
+ # to compute abilities. For this reason it's sometimes called
+ # "context" within the framework.
+ #
+ # It also stores a reference to the cache, so it can be used
+ # to cache computations by e.g. ManifestCondition.
+ attr_reader :user, :subject, :cache
+ def initialize(user, subject, opts = {})
+ @user = user
+ @subject = subject
+ @cache = opts[:cache] || {}
+ end
+
+ # helper for checking abilities on this and other subjects
+ # for the current user.
+ def can?(ability, new_subject = :_self)
+ return allowed?(ability) if new_subject == :_self
+
+ policy_for(new_subject).allowed?(ability)
+ end
+
+ # This is the main entry point for permission checks. It constructs
+ # or looks up a Runner for the given ability and asks it if it passes.
+ def allowed?(*abilities)
+ abilities.all? { |a| runner(a).pass? }
+ end
+
+ # The inverse of #allowed?, used mainly in specs.
+ def disallowed?(*abilities)
+ abilities.all? { |a| !runner(a).pass? }
+ end
+
+ # computes the given ability and prints a helpful debugging output
+ # showing which
+ def debug(ability, *a)
+ runner(ability).debug(*a)
+ end
+
+ desc "Unknown user"
+ condition(:anonymous, scope: :user, score: 0) { @user.nil? }
+
+ desc "By default"
+ condition(:default, scope: :global, score: 0) { true }
+
+ def repr
+ subject_repr =
+ if @subject.respond_to?(:id)
+ "#{@subject.class.name}/#{@subject.id}"
+ else
+ @subject.inspect
+ end
+
+ user_repr =
+ if @user
+ @user.to_reference
+ else
+ "<anonymous>"
+ end
+
+ "(#{user_repr} : #{subject_repr})"
+ end
+
+ def inspect
+ "#<#{self.class.name} #{repr}>"
+ end
+
+ # returns a Runner for the given ability, capable of computing whether
+ # the ability is allowed. Runners are cached on the policy (which itself
+ # is cached on @cache), and caches its result. This is how we perform caching
+ # at the ability level.
+ def runner(ability)
+ ability = ability.to_sym
+ @runners ||= {}
+ @runners[ability] ||=
+ begin
+ delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) }
+ own_runner = Runner.new(own_steps(ability))
+ delegated_runners.inject(own_runner, &:merge_runner)
+ end
+ end
+
+ # Helpers for caching. Used by ManifestCondition in performing condition
+ # computation.
+ #
+ # NOTE we can't use ||= here because the value might be the
+ # boolean `false`
+ def cache(key, &b)
+ return @cache[key] if cached?(key)
+ @cache[key] = yield
+ end
+
+ def cached?(key)
+ !@cache[key].nil?
+ end
+
+ # returns a ManifestCondition capable of computing itself. The computation
+ # will use our own @cache.
+ def condition(name)
+ name = name.to_sym
+ @_conditions ||= {}
+ @_conditions[name] ||=
+ begin
+ raise "invalid condition #{name}" unless self.class.conditions.key?(name)
+ ManifestCondition.new(self.class.conditions[name], self)
+ end
+ end
+
+ # used in specs - returns true if there is no possible way for any action
+ # to be allowed, determined only by the global :prevent_all rules.
+ def banned?
+ global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) }
+ !Runner.new(global_steps).pass?
+ end
+
+ # A list of other policies that we've delegated to (see `Base.delegate`)
+ def delegated_policies
+ @delegated_policies ||= self.class.delegations.transform_values do |block|
+ new_subject = instance_eval(&block)
+
+ # never delegate to nil, as that would immediately prevent_all
+ next if new_subject.nil?
+
+ policy_for(new_subject)
+ end
+ end
+
+ def policy_for(other_subject)
+ DeclarativePolicy.policy_for(@user, other_subject, cache: @cache)
+ end
+
+ protected
+
+ # constructs steps that come from this policy and not from any delegations
+ def own_steps(ability)
+ rules = self.class.configuration_for(ability)
+ rules.map { |(action, rule)| Step.new(self, rule, action) }
+ end
+ end
+end
diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb
new file mode 100644
index 00000000000..b8cc60074c7
--- /dev/null
+++ b/lib/declarative_policy/cache.rb
@@ -0,0 +1,32 @@
+module DeclarativePolicy
+ module Cache
+ class << self
+ def user_key(user)
+ return '<anonymous>' if user.nil?
+ id_for(user)
+ end
+
+ def policy_key(user, subject)
+ u = user_key(user)
+ s = subject_key(subject)
+ "/dp/policy/#{u}/#{s}"
+ end
+
+ def subject_key(subject)
+ return '<nil>' if subject.nil?
+ return subject.inspect if subject.is_a?(Symbol)
+ "#{subject.class.name}:#{id_for(subject)}"
+ end
+
+ private
+
+ def id_for(obj)
+ if obj.respond_to?(:id) && obj.id
+ obj.id.to_s
+ else
+ "##{obj.object_id}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb
new file mode 100644
index 00000000000..9d7cf6b9726
--- /dev/null
+++ b/lib/declarative_policy/condition.rb
@@ -0,0 +1,102 @@
+module DeclarativePolicy
+ # A Condition is the data structure that is created by the
+ # `condition` declaration on DeclarativePolicy::Base. It is
+ # more or less just a struct of the data passed to that
+ # declaration. It holds on to the block to be instance_eval'd
+ # on a context (instance of Base) later, via #compute.
+ class Condition
+ attr_reader :name, :description, :scope
+ attr_reader :manual_score
+ attr_reader :context_key
+ def initialize(name, opts = {}, &compute)
+ @name = name
+ @compute = compute
+ @scope = opts.fetch(:scope, :normal)
+ @description = opts.delete(:description)
+ @context_key = opts[:context_key]
+ @manual_score = opts.fetch(:score, nil)
+ end
+
+ def compute(context)
+ !!context.instance_eval(&@compute)
+ end
+
+ def key
+ "#{@context_key}/#{@name}"
+ end
+ end
+
+ # In contrast to a Condition, a ManifestCondition contains
+ # a Condition and a context object, and is capable of calculating
+ # a result itself. This is the return value of Base#condition.
+ class ManifestCondition
+ def initialize(condition, context)
+ @condition = condition
+ @context = context
+ end
+
+ # The main entry point - does this condition pass? We reach into
+ # the context's cache here so that we can share in the global
+ # cache (often RequestStore or similar).
+ def pass?
+ @context.cache(cache_key) { @condition.compute(@context) }
+ end
+
+ # Whether we've already computed this condition.
+ def cached?
+ @context.cached?(cache_key)
+ end
+
+ # This is used to score Rule::Condition. See Rule::Condition#score
+ # and Runner#steps_by_score for how scores are used.
+ #
+ # The number here is intended to represent, abstractly, how
+ # expensive it would be to calculate this condition.
+ #
+ # See #cache_key for info about @condition.scope.
+ def score
+ # If we've been cached, no computation is necessary.
+ return 0 if cached?
+
+ # Use the override from condition(score: ...) if present
+ return @condition.manual_score if @condition.manual_score
+
+ # Global scope rules are cheap due to max cache sharing
+ return 2 if @condition.scope == :global
+
+ # "Normal" rules can't share caches with any other policies
+ return 16 if @condition.scope == :normal
+
+ # otherwise, we're :user or :subject scope, so it's 4 if
+ # the caller has declared a preference
+ return 4 if @condition.scope == DeclarativePolicy.preferred_scope
+
+ # and 8 for all other :user or :subject scope conditions.
+ 8
+ end
+
+ private
+
+ # This method controls the caching for the condition. This is where
+ # the condition(scope: ...) option comes into play. Notice that
+ # depending on the scope, we may cache only by the user or only by
+ # the subject, resulting in sharing across different policy objects.
+ def cache_key
+ case @condition.scope
+ when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
+ when :user then "/dp/condition/#{@condition.key}/#{user_key}"
+ when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
+ when :global then "/dp/condition/#{@condition.key}"
+ else raise 'invalid scope'
+ end
+ end
+
+ def user_key
+ Cache.user_key(@context.user)
+ end
+
+ def subject_key
+ Cache.subject_key(@context.subject)
+ end
+ end
+end
diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb
new file mode 100644
index 00000000000..b26807a7622
--- /dev/null
+++ b/lib/declarative_policy/dsl.rb
@@ -0,0 +1,103 @@
+module DeclarativePolicy
+ # The DSL evaluation context inside rule { ... } blocks.
+ # Responsible for creating and combining Rule objects.
+ #
+ # See Base.rule
+ class RuleDsl
+ def initialize(context_class)
+ @context_class = context_class
+ end
+
+ def can?(ability)
+ Rule::Ability.new(ability)
+ end
+
+ def all?(*rules)
+ Rule::And.make(rules)
+ end
+
+ def any?(*rules)
+ Rule::Or.make(rules)
+ end
+
+ def none?(*rules)
+ ~Rule::Or.new(rules)
+ end
+
+ def cond(condition)
+ Rule::Condition.new(condition)
+ end
+
+ def delegate(delegate_name, condition)
+ Rule::DelegatedCondition.new(delegate_name, condition)
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless a.size == 0 && !block_given?
+
+ if @context_class.delegations.key?(m)
+ DelegateDsl.new(self, m)
+ else
+ cond(m.to_sym)
+ end
+ end
+ end
+
+ # Used when the name of a delegate is mentioned in
+ # the rule DSL.
+ class DelegateDsl
+ def initialize(rule_dsl, delegate_name)
+ @rule_dsl = rule_dsl
+ @delegate_name = delegate_name
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless a.size == 0 && !block_given?
+
+ @rule_dsl.delegate(@delegate_name, m)
+ end
+ end
+
+ # The return value of a rule { ... } declaration.
+ # Can call back to register rules with the containing
+ # Policy class (context_class here). See Base.rule
+ #
+ # Note that the #policy method just performs an #instance_eval,
+ # which is useful for multiple #enable or #prevent callse.
+ #
+ # Also provides a #method_missing proxy to the context
+ # class's class methods, so that helper methods can be
+ # defined and used in a #policy { ... } block.
+ class PolicyDsl
+ def initialize(context_class, rule)
+ @context_class = context_class
+ @rule = rule
+ end
+
+ def policy(&b)
+ instance_eval(&b)
+ end
+
+ def enable(*abilities)
+ @context_class.enable_when(abilities, @rule)
+ end
+
+ def prevent(*abilities)
+ @context_class.prevent_when(abilities, @rule)
+ end
+
+ def prevent_all
+ @context_class.prevent_all_when(@rule)
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless @context_class.respond_to?(m)
+
+ @context_class.__send__(m, *a, &b)
+ end
+
+ def respond_to_missing?(m)
+ @context_class.respond_to?(m) || super
+ end
+ end
+end
diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb
new file mode 100644
index 00000000000..b0754098149
--- /dev/null
+++ b/lib/declarative_policy/preferred_scope.rb
@@ -0,0 +1,28 @@
+module DeclarativePolicy
+ PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
+
+ class << self
+ def with_preferred_scope(scope, &b)
+ Thread.current[PREFERRED_SCOPE_KEY], old_scope = scope, Thread.current[PREFERRED_SCOPE_KEY]
+ yield
+ ensure
+ Thread.current[PREFERRED_SCOPE_KEY] = old_scope
+ end
+
+ def preferred_scope
+ Thread.current[PREFERRED_SCOPE_KEY]
+ end
+
+ def user_scope(&b)
+ with_preferred_scope(:user, &b)
+ end
+
+ def subject_scope(&b)
+ with_preferred_scope(:subject, &b)
+ end
+
+ def preferred_scope=(scope)
+ Thread.current[PREFERRED_SCOPE_KEY] = scope
+ end
+ end
+end
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb
new file mode 100644
index 00000000000..bfcec241489
--- /dev/null
+++ b/lib/declarative_policy/rule.rb
@@ -0,0 +1,301 @@
+module DeclarativePolicy
+ module Rule
+ # A Rule is the object that results from the `rule` declaration,
+ # usually built using the DSL in `RuleDsl`. It is a basic logical
+ # combination of building blocks, and is capable of deciding,
+ # given a context (instance of DeclarativePolicy::Base) whether it
+ # passes or not. Note that this decision doesn't by itself know
+ # how that affects the actual ability decision - for that, a
+ # `Step` is used.
+ class Base
+ def self.make(*a)
+ new(*a).simplify
+ end
+
+ # true or false whether this rule passes.
+ # `context` is a policy - an instance of
+ # DeclarativePolicy::Base.
+ def pass?(context)
+ raise 'abstract'
+ end
+
+ # same as #pass? except refuses to do any I/O,
+ # returning nil if the result is not yet cached.
+ # used for accurately scoring And/Or
+ def cached_pass?(context)
+ raise 'abstract'
+ end
+
+ # abstractly, how long would it take to compute
+ # this rule? lower-scored rules are tried first.
+ def score(context)
+ raise 'abstract'
+ end
+
+ # unwrap double negatives and nested and/or
+ def simplify
+ self
+ end
+
+ # convenience combination methods
+ def or(other)
+ Or.make([self, other])
+ end
+
+ def and(other)
+ And.make([self, other])
+ end
+
+ def negate
+ Not.make(self)
+ end
+
+ alias_method :|, :or
+ alias_method :&, :and
+ alias_method :~@, :negate
+
+ def inspect
+ "#<Rule #{repr}>"
+ end
+ end
+
+ # A rule that checks a condition. This is the
+ # type of rule that results from a basic bareword
+ # in the rule dsl (see RuleDsl#method_missing).
+ class Condition < Base
+ def initialize(name)
+ @name = name
+ end
+
+ # we delegate scoring to the condition. See
+ # ManifestCondition#score.
+ def score(context)
+ context.condition(@name).score
+ end
+
+ # Let the ManifestCondition from the context
+ # decide whether we pass.
+ def pass?(context)
+ context.condition(@name).pass?
+ end
+
+ # returns nil unless it's already cached
+ def cached_pass?(context)
+ condition = context.condition(@name)
+ return nil unless condition.cached?
+ condition.pass?
+ end
+
+ def description(context)
+ context.class.conditions[@name].description
+ end
+
+ def repr
+ @name.to_s
+ end
+ end
+
+ # A rule constructed from DelegateDsl - using a condition from a
+ # delegated policy.
+ class DelegatedCondition < Base
+ # Internal use only - this is rescued each time it's raised.
+ MissingDelegate = Class.new(StandardError)
+
+ def initialize(delegate_name, name)
+ @delegate_name = delegate_name
+ @name = name
+ end
+
+ def delegated_context(context)
+ policy = context.delegated_policies[@delegate_name]
+ raise MissingDelegate if policy.nil?
+ policy
+ end
+
+ def score(context)
+ delegated_context(context).condition(@name).score
+ rescue MissingDelegate
+ 0
+ end
+
+ def cached_pass?(context)
+ condition = delegated_context(context).condition(@name)
+ return nil unless condition.cached?
+ condition.pass?
+ rescue MissingDelegate
+ false
+ end
+
+ def pass?(context)
+ delegated_context(context).condition(@name).pass?
+ rescue MissingDelegate
+ false
+ end
+
+ def repr
+ "#{@delegate_name}.#{@name}"
+ end
+ end
+
+ # A rule constructed from RuleDsl#can?. Computes a different ability
+ # on the same subject.
+ class Ability < Base
+ attr_reader :ability
+ def initialize(ability)
+ @ability = ability
+ end
+
+ # We ask the ability's runner for a score
+ def score(context)
+ context.runner(@ability).score
+ end
+
+ def pass?(context)
+ context.allowed?(@ability)
+ end
+
+ def cached_pass?(context)
+ runner = context.runner(@ability)
+ return nil unless runner.cached?
+ runner.pass?
+ end
+
+ def description(context)
+ "User can #{@ability.inspect}"
+ end
+
+ def repr
+ "can?(#{@ability.inspect})"
+ end
+ end
+
+ # Logical `and`, containing a list of rules. Only passes
+ # if all of them do.
+ class And < Base
+ attr_reader :rules
+ def initialize(rules)
+ @rules = rules
+ end
+
+ def simplify
+ simplified_rules = @rules.flat_map do |rule|
+ simplified = rule.simplify
+ case simplified
+ when And then simplified.rules
+ else [simplified]
+ end
+ end
+
+ And.new(simplified_rules)
+ end
+
+ def score(context)
+ return 0 unless cached_pass?(context).nil?
+
+ # note that cached rules will have score 0 anyways.
+ @rules.map { |r| r.score(context) }.inject(0, :+)
+ end
+
+ def pass?(context)
+ # try to find a cached answer before
+ # checking in order
+ cached = cached_pass?(context)
+ return cached unless cached.nil?
+
+ @rules.all? { |r| r.pass?(context) }
+ end
+
+ def cached_pass?(context)
+ passes = @rules.map { |r| r.cached_pass?(context) }
+ return false if passes.any? { |p| p == false }
+ return true if passes.all? { |p| p == true }
+
+ nil
+ end
+
+ def repr
+ "all?(#{rules.map(&:repr).join(', ')})"
+ end
+ end
+
+ # Logical `or`. Mirrors And.
+ class Or < Base
+ attr_reader :rules
+ def initialize(rules)
+ @rules = rules
+ end
+
+ def pass?(context)
+ cached = cached_pass?(context)
+ return cached unless cached.nil?
+
+ @rules.any? { |r| r.pass?(context) }
+ end
+
+ def simplify
+ simplified_rules = @rules.flat_map do |rule|
+ simplified = rule.simplify
+ case simplified
+ when Or then simplified.rules
+ else [simplified]
+ end
+ end
+
+ Or.new(simplified_rules)
+ end
+
+ def cached_pass?(context)
+ passes = @rules.map { |r| r.cached_pass?(context) }
+ return true if passes.any? { |p| p == true }
+ return false if passes.all? { |p| p == false }
+
+ nil
+ end
+
+ def score(context)
+ return 0 unless cached_pass?(context).nil?
+ @rules.map { |r| r.score(context) }.inject(0, :+)
+ end
+
+ def repr
+ "any?(#{@rules.map(&:repr).join(', ')})"
+ end
+ end
+
+ class Not < Base
+ attr_reader :rule
+ def initialize(rule)
+ @rule = rule
+ end
+
+ def simplify
+ case @rule
+ when And then Or.new(@rule.rules.map(&:negate)).simplify
+ when Or then And.new(@rule.rules.map(&:negate)).simplify
+ when Not then @rule.rule.simplify
+ else Not.new(@rule.simplify)
+ end
+ end
+
+ def pass?(context)
+ !@rule.pass?(context)
+ end
+
+ def cached_pass?(context)
+ case @rule.cached_pass?(context)
+ when nil then nil
+ when true then false
+ when false then true
+ end
+ end
+
+ def score(context)
+ @rule.score(context)
+ end
+
+ def repr
+ "~#{@rule.repr}"
+ end
+ end
+ end
+end
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
new file mode 100644
index 00000000000..b5c615da4e3
--- /dev/null
+++ b/lib/declarative_policy/runner.rb
@@ -0,0 +1,181 @@
+module DeclarativePolicy
+ class Runner
+ class State
+ def initialize
+ @enabled = false
+ @prevented = false
+ end
+
+ def enable!
+ @enabled = true
+ end
+
+ def enabled?
+ @enabled
+ end
+
+ def prevent!
+ @prevented = true
+ end
+
+ def prevented?
+ @prevented
+ end
+
+ def pass?
+ !prevented? && enabled?
+ end
+ end
+
+ # a Runner contains a list of Steps to be run.
+ attr_reader :steps
+ def initialize(steps)
+ @steps = steps
+ end
+
+ # We make sure only to run any given Runner once,
+ # and just continue to use the resulting @state
+ # that's left behind.
+ def cached?
+ !!@state
+ end
+
+ # used by Rule::Ability. See #steps_by_score
+ def score
+ return 0 if cached?
+ steps.map(&:score).inject(0, :+)
+ end
+
+ def merge_runner(other)
+ Runner.new(@steps + other.steps)
+ end
+
+ # The main entry point, called for making an ability decision.
+ # See #run and DeclarativePolicy::Base#can?
+ def pass?
+ run unless cached?
+
+ @state.pass?
+ end
+
+ # see DeclarativePolicy::Base#debug
+ def debug(out = $stderr)
+ run(out)
+ end
+
+ private
+
+ def flatten_steps!
+ @steps = @steps.flat_map { |s| s.flattened(@steps) }
+ end
+
+ # This method implements the semantic of "one enable and no prevents".
+ # It relies on #steps_by_score for the main loop, and updates @state
+ # with the result of the step.
+ def run(debug = nil)
+ @state = State.new
+
+ steps_by_score do |step, score|
+ passed = nil
+ case step.action
+ when :enable then
+ # we only check :enable actions if they have a chance of
+ # changing the outcome - if no other rule has enabled or
+ # prevented.
+ unless @state.enabled? || @state.prevented?
+ passed = step.pass?
+ @state.enable! if passed
+ end
+
+ debug << inspect_step(step, score, passed) if debug
+ when :prevent then
+ # we only check :prevent actions if the state hasn't already
+ # been prevented.
+ unless @state.prevented?
+ passed = step.pass?
+ if passed
+ @state.prevent!
+ return unless debug
+ end
+ end
+
+ debug << inspect_step(step, score, passed) if debug
+ else raise "invalid action #{step.action.inspect}"
+ end
+ end
+
+ @state
+ end
+
+ # This is the core spot where all those `#score` methods matter.
+ # It is critcal for performance to run steps in the correct order,
+ # so that we don't compute expensive conditions (potentially n times
+ # if we're called on, say, a large list of users).
+ #
+ # In order to determine the cheapest step to run next, we rely on
+ # Step#score, which returns a numerical rating of how expensive
+ # it would be to calculate - the lower the better. It would be
+ # easy enough to statically sort by these scores, but we can do
+ # a little better - the scores are cache-aware (conditions that
+ # are already in the cache have score 0), which means that running
+ # a step can actually change the scores of other steps.
+ #
+ # So! The way we sort here involves re-scoring at every step. This
+ # is by necessity quadratic, but most of the time the number of steps
+ # will be low. But just in case, if the number of steps exceeds 50,
+ # we print a warning and fall back to a static sort.
+ #
+ # For each step, we yield the step object along with the computed score
+ # for debugging purposes.
+ def steps_by_score(&b)
+ flatten_steps!
+
+ if @steps.size > 50
+ warn "DeclarativePolicy: large number of steps (#{steps.size}), falling back to static sort"
+
+ @steps.map { |s| [s.score, s] }.sort_by { |(score, _)| score }.each do |(score, step)|
+ yield step, score
+ end
+
+ return
+ end
+
+ steps = Set.new(@steps)
+
+ loop do
+ return if steps.empty?
+
+ # if the permission hasn't yet been enabled and we only have
+ # prevent steps left, we short-circuit the state here
+ @state.prevent! if !@state.enabled? && steps.all?(&:prevent?)
+
+ lowest_score = Float::INFINITY
+ next_step = nil
+
+ steps.each do |step|
+ score = step.score
+ if score < lowest_score
+ next_step = step
+ lowest_score = score
+ end
+ end
+
+ steps.delete(next_step)
+
+ yield next_step, lowest_score
+ end
+ end
+
+ # Formatter for debugging output.
+ def inspect_step(step, original_score, passed)
+ symbol =
+ case passed
+ when true then '+'
+ when false then '-'
+ when nil then ' '
+ end
+
+ "#{symbol} [#{original_score.to_i}] #{step.repr}\n"
+ end
+ end
+end
diff --git a/lib/declarative_policy/step.rb b/lib/declarative_policy/step.rb
new file mode 100644
index 00000000000..3469fe9f991
--- /dev/null
+++ b/lib/declarative_policy/step.rb
@@ -0,0 +1,86 @@
+module DeclarativePolicy
+ # This object represents one step in the runtime decision of whether
+ # an ability is allowed. It contains a Rule and a context (instance
+ # of DeclarativePolicy::Base), which contains the user, the subject,
+ # and the cache. It also contains an "action", which is the symbol
+ # :prevent or :enable.
+ class Step
+ attr_reader :context, :rule, :action
+ def initialize(context, rule, action)
+ @context = context
+ @rule = rule
+ @action = action
+ end
+
+ # In the flattening process, duplicate steps may be generated in the
+ # same rule. This allows us to eliminate those (see Runner#steps_by_score
+ # and note its use of a Set)
+ def ==(other)
+ @context == other.context && @rule == other.rule && @action == other.action
+ end
+
+ # In the runner, steps are sorted dynamically by score, so that
+ # we are sure to compute them in close to the optimal order.
+ #
+ # See also Rule#score, ManifestCondition#score, and Runner#steps_by_score.
+ def score
+ # we slightly prefer the preventative actions
+ # since they are more likely to short-circuit
+ case @action
+ when :prevent
+ @rule.score(@context) * (7.0 / 8)
+ when :enable
+ @rule.score(@context)
+ end
+ end
+
+ def with_action(action)
+ Step.new(@context, @rule, action)
+ end
+
+ def enable?
+ @action == :enable
+ end
+
+ def prevent?
+ @action == :prevent
+ end
+
+ # This rather complex method allows us to split rules into parts so that
+ # they can be sorted independently for better optimization
+ def flattened(roots)
+ case @rule
+ when Rule::Or
+ # A single `Or` step is the same as each of its elements as separate steps
+ @rule.rules.flat_map { |r| Step.new(@context, r, @action).flattened(roots) }
+ when Rule::Ability
+ # This looks like a weird micro-optimization but it buys us quite a lot
+ # in some cases. If we depend on an Ability (i.e. a `can?(...)` rule),
+ # and that ability *only* has :enable actions (modulo some actions that
+ # we already have taken care of), then its rules can be safely inlined.
+ steps = @context.runner(@rule.ability).steps.reject { |s| roots.include?(s) }
+
+ if steps.all?(&:enable?)
+ # in the case that we are a :prevent step, each inlined step becomes
+ # an independent :prevent, even though it was an :enable in its initial
+ # context.
+ steps.map! { |s| s.with_action(:prevent) } if prevent?
+
+ steps.flat_map { |s| s.flattened(roots) }
+ else
+ [self]
+ end
+ else
+ [self]
+ end
+ end
+
+ def pass?
+ @rule.pass?(@context)
+ end
+
+ def repr
+ "#{@action} when #{@rule.repr} (#{@context.repr})"
+ end
+ end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index dd864eea3fa..721ed97bb6b 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -126,8 +126,7 @@ module ExtractsPath
raise InvalidPathError unless @commit
@hex_path = Digest::SHA1.hexdigest(@path)
- @logs_path = logs_file_namespace_project_ref_path(@project.namespace,
- @project, @ref, @path)
+ @logs_path = logs_file_project_ref_path(@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
render_404
diff --git a/lib/feature.rb b/lib/feature.rb
index d3d972564af..4bd29aed687 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -12,6 +12,8 @@ class Feature
end
class << self
+ delegate :group, to: :flipper
+
def all
flipper.features.to_a
end
@@ -27,16 +29,24 @@ class Feature
all.map(&:name).include?(feature.name)
end
- def enabled?(key)
- get(key).enabled?
+ def enabled?(key, thing = nil)
+ get(key).enabled?(thing)
+ end
+
+ def enable(key, thing = true)
+ get(key).enable(thing)
end
- def enable(key)
- get(key).enable
+ def disable(key, thing = false)
+ get(key).disable(thing)
end
- def disable(key)
- get(key).disable
+ def enable_group(key, group)
+ get(key).enable_group(group)
+ end
+
+ def disable_group(key, group)
+ get(key).disable_group(group)
end
def flipper
@@ -47,5 +57,11 @@ class Feature
Flipper.new(adapter)
end
end
+
+ # This method is called from config/initializers/flipper.rb and can be used
+ # to register Flipper groups.
+ # See https://docs.gitlab.com/ee/development/feature_flags.html#feature-groups
+ def register_feature_groups
+ end
end
end
diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb
index e4f7cad2b79..45c2b01dd8f 100644
--- a/lib/gitlab/allowable.rb
+++ b/lib/gitlab/allowable.rb
@@ -1,7 +1,7 @@
module Gitlab
module Allowable
- def can?(user, action, subject = :global)
- Ability.allowed?(user, action, subject)
+ def can?(*args)
+ Ability.allowed?(*args)
end
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 3933c3b04dd..ccb5d886bab 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -130,13 +130,13 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
- if token && valid_scoped_token?(token, AVAILABLE_SCOPES.map(&:to_s))
+ if token && valid_scoped_token?(token, AVAILABLE_SCOPES)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
end
end
def valid_oauth_token?(token)
- token && token.accessible? && valid_scoped_token?(token, ["api"])
+ token && token.accessible? && valid_scoped_token?(token, [:api])
end
def valid_scoped_token?(token, scopes)
diff --git a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
new file mode 100644
index 00000000000..91540127ea9
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module BackgroundMigration
+ class MigrateBuildStageIdReference
+ def perform(start_id, stop_id)
+ sql = <<-SQL.strip_heredoc
+ UPDATE ci_builds
+ SET stage_id =
+ (SELECT id FROM ci_stages
+ WHERE ci_stages.pipeline_id = ci_builds.commit_id
+ AND ci_stages.name = ci_builds.stage)
+ WHERE ci_builds.id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
+ AND ci_builds.stage_id IS NULL
+ SQL
+
+ ActiveRecord::Base.connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb
index f87a7b7942e..2ee35a0d4c1 100644
--- a/lib/gitlab/badge/build/metadata.rb
+++ b/lib/gitlab/badge/build/metadata.rb
@@ -15,12 +15,11 @@ module Gitlab
end
def image_url
- build_namespace_project_badges_url(@project.namespace,
- @project, @ref, format: :svg)
+ build_project_badges_url(@project, @ref, format: :svg)
end
def link_url
- namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ project_commits_url(@project, id: @ref)
end
end
end
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
index 53588185622..e898f5d790e 100644
--- a/lib/gitlab/badge/coverage/metadata.rb
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -16,13 +16,11 @@ module Gitlab
end
def image_url
- coverage_namespace_project_badges_url(@project.namespace,
- @project, @ref,
- format: :svg)
+ coverage_project_badges_url(@project, @ref, format: :svg)
end
def link_url
- namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ project_commits_url(@project, @ref)
end
end
end
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
index 4a049ef758d..8ad6f3cb986 100644
--- a/lib/gitlab/badge/metadata.rb
+++ b/lib/gitlab/badge/metadata.rb
@@ -4,7 +4,7 @@ module Gitlab
# Abstract class for badge metadata
#
class Metadata
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index f074df9c7a1..d7e09acbbf3 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -7,11 +7,14 @@ module Gitlab
#
class Cache < Node
include Configurable
+ include Attributable
- ALLOWED_KEYS = %i[key untracked paths].freeze
+ ALLOWED_KEYS = %i[key untracked paths policy].freeze
+ DEFAULT_POLICY = 'pull-push'.freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
+ validates :policy, inclusion: { in: %w[pull-push push pull], message: 'should be pull-push, push, or pull' }, allow_blank: true
end
entry :key, Entry::Key,
@@ -25,8 +28,15 @@ module Gitlab
helpers :key
+ attributes :policy
+
def value
- super.merge(key: key_value)
+ result = super
+
+ result[:key] = key_value
+ result[:policy] = policy || DEFAULT_POLICY
+
+ result
end
end
end
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 897dcff8012..6555c589173 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -15,7 +15,7 @@ module Gitlab
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
- validates :entrypoint, type: String, allow_nil: true
+ validates :entrypoint, array_of_strings: true, allow_nil: true
end
def hash?
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index b52faf48b58..3e2ebcff31a 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -15,8 +15,8 @@ module Gitlab
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
- validates :entrypoint, type: String, allow_nil: true
- validates :command, type: String, allow_nil: true
+ validates :entrypoint, array_of_strings: true, allow_nil: true
+ validates :command, array_of_strings: true, allow_nil: true
validates :alias, type: String, allow_nil: true
end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 439ef0ce015..8ad3e57e59d 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -12,9 +12,7 @@ module Gitlab
end
def action_path
- cancel_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ cancel_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
index b173c23fba4..c0c7c7f5b5d 100644
--- a/lib/gitlab/ci/status/build/common.rb
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -8,9 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ project_job_path(subject.project, subject)
end
end
end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index e80f3263794..c7726543599 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -20,9 +20,7 @@ module Gitlab
end
def action_path
- play_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ play_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 56303e4cb17..8c8fdc56d75 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -16,9 +16,7 @@ module Gitlab
end
def action_path
- retry_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ retry_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 2778d6f3b52..d464738deaf 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -20,9 +20,7 @@ module Gitlab
end
def action_path
- play_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ play_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 76bfd18bf40..61bb07beb0f 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -8,9 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_pipeline_path(subject.project.namespace,
- subject.project,
- subject)
+ project_pipeline_path(subject.project, subject)
end
def has_action?
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
index 7852f492e1d..bc99d925347 100644
--- a/lib/gitlab/ci/status/stage/common.rb
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -8,10 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_pipeline_path(subject.project.namespace,
- subject.project,
- subject.pipeline,
- anchor: subject.name)
+ project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name)
end
def has_action?
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index 75a213ef752..98dfe900044 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -1,7 +1,7 @@
module Gitlab
module Conflict
class File
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include IconsHelper
MissingResolution = Class.new(ResolutionError)
@@ -205,9 +205,7 @@ module Gitlab
old_path: their_path,
new_path: our_path,
blob_icon: file_type_icon_class('file', our_mode, our_path),
- blob_path: namespace_project_blob_path(merge_request.project.namespace,
- merge_request.project,
- ::File.join(merge_request.diff_refs.head_sha, our_path))
+ blob_path: project_blob_path(merge_request.project, ::File.join(merge_request.diff_refs.head_sha, our_path))
}
json_hash.tap do |json_hash|
@@ -223,11 +221,10 @@ module Gitlab
end
def content_path
- conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- old_path: their_path,
- new_path: our_path)
+ conflict_for_path_project_merge_request_path(merge_request.project,
+ merge_request,
+ old_path: their_path,
+ new_path: our_path)
end
# Don't try to print merge_request or repository.
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 7f1932da3b7..7fa02f3d7b3 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -33,12 +33,7 @@ module Gitlab
def uncached_application_settings
return fake_application_settings unless connect_to_db?
- # This loads from the database into the cache, so handle Redis errors
- begin
- db_settings = ::ApplicationSetting.current
- rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL
- # In case Redis isn't running or the Redis UNIX socket file is not available
- end
+ db_settings = ::ApplicationSetting.current
# If there are pending migrations, it's possible there are columns that
# need to be added to the application settings. To prevent Rake tasks
diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb
index 9d25ef078e8..f5d08c0b658 100644
--- a/lib/gitlab/cycle_analytics/metrics_tables.rb
+++ b/lib/gitlab/cycle_analytics/metrics_tables.rb
@@ -13,6 +13,10 @@ module Gitlab
MergeRequestDiff.arel_table
end
+ def mr_diff_commits_table
+ MergeRequestDiffCommit.arel_table
+ end
+
def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 7d342a2d2cb..b260822788d 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -2,40 +2,59 @@ module Gitlab
module CycleAnalytics
class PlanEventFetcher < BaseEventFetcher
def initialize(*args)
- @projections = [mr_diff_table[:st_commits].as('commits'),
+ @projections = [mr_diff_table[:id],
+ mr_diff_table[:st_commits],
issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args)
end
def events_query
- base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
+ base_query
+ .join(mr_diff_table)
+ .on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
super
end
private
+ def merge_request_diff_commits
+ @merge_request_diff_commits ||=
+ MergeRequestDiffCommit
+ .where(merge_request_diff_id: event_result.map { |event| event['id'] })
+ .group_by(&:merge_request_diff_id)
+ end
+
def serialize(event)
- st_commit = first_time_reference_commit(event.delete('commits'), event)
+ commit = first_time_reference_commit(event)
- return unless st_commit
+ return unless commit
- serialize_commit(event, st_commit, query)
+ serialize_commit(event, commit, query)
end
- def first_time_reference_commit(commits, event)
+ def first_time_reference_commit(event)
+ return nil unless event && merge_request_diff_commits
+
+ commits =
+ if event['st_commits'].present?
+ YAML.load(event['st_commits'])
+ else
+ merge_request_diff_commits[event['id'].to_i]
+ end
+
return nil if commits.blank?
- YAML.load(commits).find do |commit|
+ commits.find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end
end
- def serialize_commit(event, st_commit, query)
- commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
+ def serialize_commit(event, commit, query)
+ commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
index 89530082cd2..f333ff22300 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
@@ -29,6 +29,11 @@ module Gitlab
paths = Array(paths)
RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
end
+
+ def revert_renames
+ RenameProjects.new([], self).revert_renames
+ RenameNamespaces.new([], self).revert_renames
+ end
end
end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index d8163d7da11..33f8939bc61 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -6,7 +6,10 @@ module Gitlab
attr_reader :paths, :migration
delegate :update_column_in_batches,
+ :execute,
:replace_sql,
+ :quote_string,
+ :say,
to: :migration
def initialize(paths, migration)
@@ -26,24 +29,45 @@ module Gitlab
new_path = rename_path(namespace_path, old_path)
new_full_path = join_routable_path(namespace_path, new_path)
+ perform_rename(routable, old_full_path, new_full_path)
+
+ [old_full_path, new_full_path]
+ end
+
+ def perform_rename(routable, old_full_path, new_full_path)
# skips callbacks & validations
+ new_path = new_full_path.split('/').last
routable.class.where(id: routable)
.update_all(path: new_path)
rename_routes(old_full_path, new_full_path)
-
- [old_full_path, new_full_path]
end
def rename_routes(old_full_path, new_full_path)
+ routes = Route.arel_table
+
+ quoted_old_full_path = quote_string(old_full_path)
+ quoted_old_wildcard_path = quote_string("#{old_full_path}/%")
+
+ filter = if Database.mysql?
+ "lower(routes.path) = lower('#{quoted_old_full_path}') "\
+ "OR routes.path LIKE '#{quoted_old_wildcard_path}'"
+ else
+ "routes.id IN "\
+ "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
+ "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
+ end
+
replace_statement = replace_sql(Route.arel_table[:path],
old_full_path,
new_full_path)
- update_column_in_batches(:routes, :path, replace_statement) do |table, query|
- path_or_children = table[:path].matches_any([old_full_path, "#{old_full_path}/%"])
- query.where(path_or_children)
- end
+ update = Arel::UpdateManager.new(ActiveRecord::Base)
+ .table(routes)
+ .set([[routes[:path], replace_statement]])
+ .where(Arel::Nodes::SqlLiteral.new(filter))
+
+ execute(update.to_sql)
end
def rename_path(namespace_path, path_was)
@@ -86,32 +110,74 @@ module Gitlab
def move_folders(directory, old_relative_path, new_relative_path)
old_path = File.join(directory, old_relative_path)
- return unless File.directory?(old_path)
+ unless File.directory?(old_path)
+ say "#{old_path} doesn't exist, skipping"
+ return
+ end
new_path = File.join(directory, new_relative_path)
FileUtils.mv(old_path, new_path)
end
def remove_cached_html_for_projects(project_ids)
- update_column_in_batches(:projects, :description_html, nil) do |table, query|
- query.where(table[:id].in(project_ids))
- end
-
- update_column_in_batches(:issues, :description_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
+ project_ids.each do |project_id|
+ update_column_in_batches(:projects, :description_html, nil) do |table, query|
+ query.where(table[:id].eq(project_id))
+ end
+
+ update_column_in_batches(:issues, :description_html, nil) do |table, query|
+ query.where(table[:project_id].eq(project_id))
+ end
+
+ update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
+ query.where(table[:target_project_id].eq(project_id))
+ end
+
+ update_column_in_batches(:notes, :note_html, nil) do |table, query|
+ query.where(table[:project_id].eq(project_id))
+ end
+
+ update_column_in_batches(:milestones, :description_html, nil) do |table, query|
+ query.where(table[:project_id].eq(project_id))
+ end
end
+ end
- update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
- query.where(table[:target_project_id].in(project_ids))
+ def track_rename(type, old_path, new_path)
+ key = redis_key_for_type(type)
+ Gitlab::Redis.with do |redis|
+ redis.lpush(key, [old_path, new_path].to_json)
+ redis.expire(key, 2.weeks.to_i)
end
+ say "tracked rename: #{key}: #{old_path} -> #{new_path}"
+ end
- update_column_in_batches(:notes, :note_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
+ def reverts_for_type(type)
+ key = redis_key_for_type(type)
+
+ Gitlab::Redis.with do |redis|
+ failed_reverts = []
+
+ while rename_info = redis.lpop(key)
+ path_before_rename, path_after_rename = JSON.parse(rename_info)
+ say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}"
+ begin
+ yield(path_before_rename, path_after_rename)
+ rescue StandardError => e
+ failed_reverts << rename_info
+ say "Renaming #{type} from #{path_after_rename} back to "\
+ "#{path_before_rename} failed. Review the error and try "\
+ "again by running the `down` action. \n"\
+ "#{e.message}: \n #{e.backtrace.join("\n")}"
+ end
+ end
+
+ failed_reverts.each { |rename_info| redis.lpush(key, rename_info) }
end
+ end
- update_column_in_batches(:milestones, :description_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
- end
+ def redis_key_for_type(type)
+ "rename:#{migration.name}:#{type}"
end
def file_storage?
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index da7e2cb2e85..05b86f32ce2 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -26,6 +26,12 @@ module Gitlab
def rename_namespace(namespace)
old_full_path, new_full_path = rename_path_for_routable(namespace)
+ track_rename('namespace', old_full_path, new_full_path)
+
+ rename_namespace_dependencies(namespace, old_full_path, new_full_path)
+ end
+
+ def rename_namespace_dependencies(namespace, old_full_path, new_full_path)
move_repositories(namespace, old_full_path, new_full_path)
move_uploads(old_full_path, new_full_path)
move_pages(old_full_path, new_full_path)
@@ -33,6 +39,23 @@ module Gitlab
remove_cached_html_for_projects(projects_for_namespace(namespace).map(&:id))
end
+ def revert_renames
+ reverts_for_type('namespace') do |path_before_rename, current_path|
+ matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
+ namespace = MigrationClasses::Namespace.joins(:route)
+ .where(matches_path).first&.becomes(MigrationClasses::Namespace)
+
+ if namespace
+ perform_rename(namespace, current_path, path_before_rename)
+
+ rename_namespace_dependencies(namespace, current_path, path_before_rename)
+ else
+ say "Couldn't rename namespace from #{current_path} back to #{path_before_rename}, "\
+ "namespace was renamed, or no longer exists at the expected path"
+ end
+ end
+ end
+
def rename_user(old_username, new_username)
MigrationClasses::User.where(username: old_username)
.update_all(username: new_username)
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 448717eb744..75a75f61953 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -16,12 +16,37 @@ module Gitlab
def rename_project(project)
old_full_path, new_full_path = rename_path_for_routable(project)
+ track_rename('project', old_full_path, new_full_path)
+
+ move_project_folders(project, old_full_path, new_full_path)
+ end
+
+ def move_project_folders(project, old_full_path, new_full_path)
move_repository(project, old_full_path, new_full_path)
move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
move_uploads(old_full_path, new_full_path)
move_pages(old_full_path, new_full_path)
end
+ def revert_renames
+ reverts_for_type('project') do |path_before_rename, current_path|
+ matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
+ project = MigrationClasses::Project.joins(:route)
+ .where(matches_path).first
+
+ if project
+ perform_rename(project, current_path, path_before_rename)
+
+ move_project_folders(project, current_path, path_before_rename)
+ else
+ say "Couldn't rename project from #{current_path} back to "\
+ "#{path_before_rename}, project was renamed or no longer "\
+ "exists at the expected path."
+
+ end
+ end
+ end
+
def move_repository(project, old_path, new_path)
unless gitlab_shell.mv_repository(project.repository_storage_path,
old_path,
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
new file mode 100644
index 00000000000..d9400e04b83
--- /dev/null
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ module Database
+ BINARY_TYPE = if Gitlab::Database.postgresql?
+ # PostgreSQL defines its own class with slightly different
+ # behaviour from the default Binary type.
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
+ else
+ ActiveRecord::Type::Binary
+ end
+
+ # Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
+ #
+ # Using ShaAttribute allows you to store SHA1 values as binary while still
+ # using them as if they were stored as string values. This gives you the
+ # ease of use of string values, but without the storage overhead.
+ class ShaAttribute < BINARY_TYPE
+ PACK_FORMAT = 'H*'.freeze
+
+ # Casts binary data to a SHA1 in hexadecimal.
+ def type_cast_from_database(value)
+ value = super
+
+ value ? value.unpack(PACK_FORMAT)[0] : nil
+ end
+
+ # Casts a SHA1 in hexadecimal to the proper binary format.
+ def type_cast_for_database(value)
+ arg = value ? [value].pack(PACK_FORMAT) : nil
+
+ super(arg)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 7bbd154eb03..d2360583741 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -52,7 +52,7 @@ module Gitlab
# # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
def link_regex(regex, &url_proc)
highlighted_lines.map!.with_index do |rich_line, i|
- marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
+ marker = StringRegexMarker.new(plain_lines[i].chomp, rich_line.html_safe)
marker.mark(regex, group: :name) do |text, left:, right:|
url = yield(text)
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 1a5887dab7e..edd7795eef0 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -337,7 +337,7 @@ module Gitlab
# In the EE repo
$ git push origin #{ee_branch_prefix}
- ⚠️ Also, don't forget to create a new merge request on gitlab-ce and
+ ⚠️ Also, don't forget to create a new merge request on gitlab-ee and
cross-link it with the CE merge request.
Once this is done, you can retry this failed build, and it should pass.
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index ea035e33eff..dd1d9dcd555 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -4,7 +4,7 @@ module Gitlab
class RepositoryPush
attr_reader :author_id, :ref, :action
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include DiffHelper
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
@@ -96,20 +96,13 @@ module Gitlab
def target_url
if @action == :push && commits
if commits.length > 1
- namespace_project_compare_url(project_namespace,
- project,
- from: compare.start_commit,
- to: compare.head_commit)
+ project_compare_url(project, from: compare.start_commit, to: compare.head_commit)
else
- namespace_project_commit_url(project_namespace,
- project,
- commits.first)
+ project_commit_url(project, commits.first)
end
else
unless @action == :delete
- namespace_project_tree_url(project_namespace,
- project,
- ref_name)
+ project_tree_url(project, ref_name)
end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 936606152e9..4175746be39 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -7,8 +7,10 @@ module Gitlab
CommandError = Class.new(StandardError)
class << self
+ include Gitlab::EncodingHelper
+
def ref_name(ref)
- ref.sub(/\Arefs\/(tags|heads)\//, '')
+ encode! ref.sub(/\Arefs\/(tags|heads)\//, '')
end
def branch_name(ref)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index a7aceab4c14..ea386c2ddcb 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -41,10 +41,6 @@ module Gitlab
commit_id: sha
)
when :BLOB
- # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
- # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
- # which is what we use below to keep a consistent behavior.
- detect = CharlockHolmes::EncodingDetector.new(8000).detect(entry.data)
new(
id: entry.oid,
name: name,
@@ -53,7 +49,7 @@ module Gitlab
mode: entry.mode.to_s(8),
path: path,
commit_id: sha,
- binary: detect && detect[:type] == :binary
+ binary: binary?(entry.data)
)
end
end
@@ -87,14 +83,28 @@ module Gitlab
end
def raw(repository, sha)
- blob = repository.lookup(sha)
+ Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled|
+ if is_enabled
+ Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
+ else
+ blob = repository.lookup(sha)
+
+ new(
+ id: blob.oid,
+ size: blob.size,
+ data: blob.content(MAX_DATA_DISPLAY_SIZE),
+ binary: blob.binary?
+ )
+ end
+ end
+ end
- new(
- id: blob.oid,
- size: blob.size,
- data: blob.content(MAX_DATA_DISPLAY_SIZE),
- binary: blob.binary?
- )
+ def binary?(data)
+ # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
+ # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
+ # which is what we use below to keep a consistent behavior.
+ detect = CharlockHolmes::EncodingDetector.new(8000).detect(data)
+ detect && detect[:type] == :binary
end
# Recursive search of blob id by path
@@ -165,8 +175,17 @@ module Gitlab
return if @data == '' # don't mess with submodule blobs
return @data if @loaded_all_data
+ Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
+ @data = begin
+ if is_enabled
+ Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: id, limit: -1).data
+ else
+ repository.lookup(id).content
+ end
+ end
+ end
+
@loaded_all_data = true
- @data = repository.lookup(id).content
@loaded_size = @data.bytesize
@binary = nil
end
@@ -175,6 +194,10 @@ module Gitlab
encode! @name
end
+ def path
+ encode! @path
+ end
+
def truncated?
size && (size > loaded_size)
end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index bd90d24a2ec..5042916343b 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -4,9 +4,10 @@ module Gitlab
GL_PROTOCOL = 'web'.freeze
attr_reader :name, :repo_path, :path
- def initialize(name, repo_path)
+ def initialize(name, project)
@name = name
- @repo_path = repo_path
+ @project = project
+ @repo_path = project.repository.path
@path = File.join(repo_path.strip, 'hooks', name)
end
@@ -38,7 +39,8 @@ module Gitlab
vars = {
'GL_ID' => gl_id,
'PWD' => repo_path,
- 'GL_PROTOCOL' => GL_PROTOCOL
+ 'GL_PROTOCOL' => GL_PROTOCOL,
+ 'GL_REPOSITORY' => Gitlab::GlRepository.gl_repository(@project, false)
}
options = {
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
index 1add037fa5f..666743006e5 100644
--- a/lib/gitlab/git/index.rb
+++ b/lib/gitlab/git/index.rb
@@ -110,10 +110,6 @@ module Gitlab
if segment == '..'
raise IndexError, 'Path cannot include directory traversal'
end
-
- unless segment =~ Gitlab::Regex.file_name_regex
- raise IndexError, "Path #{Gitlab::Regex.file_name_regex_message}"
- end
end
pathname.to_s
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 0a0c6f76cd3..e51966313d4 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -113,9 +113,7 @@ module Gitlab
def local_branches(sort_by: nil)
gitaly_migrate(:local_branches) do |is_enabled|
if is_enabled
- gitaly_ref_client.local_branches(sort_by: sort_by).map do |gitaly_branch|
- Gitlab::Git::Branch.new(self, gitaly_branch.name, gitaly_branch)
- end
+ gitaly_ref_client.local_branches(sort_by: sort_by)
else
branches(filter: :local, sort_by: sort_by)
end
@@ -549,41 +547,38 @@ module Gitlab
rugged.rev_parse(oid_or_ref_name)
end
- # Return hash with submodules info for this repository
+ # Returns url for submodule
#
# Ex.
- # {
- # "current_path/rack" => {
- # "name" => "original_path/rack",
- # "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
- # "url" => "git://github.com/chneukirchen/rack.git"
- # },
- # "encoding" => {
- # "id" => ....
- # }
- # }
+ # @repository.submodule_url_for('master', 'rack')
+ # # => git@localhost:rack.git
#
- def submodules(ref)
- commit = rev_parse_target(ref)
- return {} unless commit
-
- begin
- content = blob_content(commit, ".gitmodules")
- rescue InvalidBlobName
- return {}
+ def submodule_url_for(ref, path)
+ Gitlab::GitalyClient.migrate(:submodule_url_for) do |is_enabled|
+ if is_enabled
+ gitaly_submodule_url_for(ref, path)
+ else
+ if submodules(ref).any?
+ submodule = submodules(ref)[path]
+ submodule['url'] if submodule
+ end
+ end
end
-
- parser = GitmodulesParser.new(content)
- fill_submodule_ids(commit, parser.parse)
end
# Return total commits count accessible from passed ref
def commit_count(ref)
- walker = Rugged::Walker.new(rugged)
- walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
- oid = rugged.rev_parse_oid(ref)
- walker.push(oid)
- walker.count
+ gitaly_migrate(:commit_count) do |is_enabled|
+ if is_enabled
+ gitaly_commit_client.commit_count(ref)
+ else
+ walker = Rugged::Walker.new(rugged)
+ walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
+ oid = rugged.rev_parse_oid(ref)
+ walker.push(oid)
+ walker.count
+ end
+ end
end
# Sets HEAD to the commit specified by +ref+; +ref+ can be a branch or
@@ -912,6 +907,35 @@ module Gitlab
private
+ # We are trying to deprecate this method because it does a lot of work
+ # but it seems to be used only to look up submodule URL's.
+ # https://gitlab.com/gitlab-org/gitaly/issues/329
+ def submodules(ref)
+ commit = rev_parse_target(ref)
+ return {} unless commit
+
+ begin
+ content = blob_content(commit, ".gitmodules")
+ rescue InvalidBlobName
+ return {}
+ end
+
+ parser = GitmodulesParser.new(content)
+ fill_submodule_ids(commit, parser.parse)
+ end
+
+ def gitaly_submodule_url_for(ref, path)
+ # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited.
+ commit_object = gitaly_commit_client.tree_entry(ref, path, 1)
+
+ return unless commit_object && commit_object.type == :COMMIT
+
+ gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Blob::MAX_DATA_DISPLAY_SIZE)
+ found_module = GitmodulesParser.new(gitmodules.data).parse[path]
+
+ found_module && found_module['url']
+ end
+
def alternate_object_directories
Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact
end
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index b9afa05c819..b6d4e6cfe46 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -80,6 +80,10 @@ module Gitlab
encode! @name
end
+ def path
+ encode! @path
+ end
+
def dir?
type == :tree
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index f605c06dfc3..197a94487eb 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -70,12 +70,8 @@ module Gitlab
params['gitaly_token'].presence || Gitlab.config.gitaly['token']
end
- def self.enabled?
- Gitlab.config.gitaly.enabled
- end
-
def self.feature_enabled?(feature, status: MigrationStatus::OPT_IN)
- return false if !enabled? || status == MigrationStatus::DISABLED
+ return false if status == MigrationStatus::DISABLED
feature = Feature.get("gitaly_#{feature}")
diff --git a/lib/gitlab/gitaly_client/blob.rb b/lib/gitlab/gitaly_client/blob.rb
new file mode 100644
index 00000000000..0c398b46a08
--- /dev/null
+++ b/lib/gitlab/gitaly_client/blob.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module GitalyClient
+ class Blob
+ def initialize(repository)
+ @gitaly_repo = repository.gitaly_repository
+ end
+
+ def get_blob(oid:, limit:)
+ request = Gitaly::GetBlobRequest.new(
+ repository: @gitaly_repo,
+ oid: oid,
+ limit: limit
+ )
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request)
+
+ blob = response.first
+ return unless blob.oid.present?
+
+ data = response.reduce(blob.data.dup) { |memo, msg| memo << msg.data.dup }
+
+ Gitlab::Git::Blob.new(
+ id: blob.oid,
+ size: blob.size,
+ data: data,
+ binary: Gitlab::Git::Blob.binary?(data)
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index b8877619797..aafc0520664 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -56,6 +56,15 @@ module Gitlab
entry
end
+ def commit_count(ref)
+ request = Gitaly::CountCommitsRequest.new(
+ repository: @gitaly_repo,
+ revision: ref
+ )
+
+ GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count
+ end
+
private
def commit_diff_request_params(commit, options = {})
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index 6d5f54dd959..6edc69de078 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -1,8 +1,11 @@
module Gitlab
module GitalyClient
class Ref
+ include Gitlab::EncodingHelper
+
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
+ @repository = repository
@gitaly_repo = repository.gitaly_repository
@storage = repository.storage
end
@@ -16,13 +19,13 @@ module Gitlab
def branch_names
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request)
- consume_refs_response(response, prefix: 'refs/heads/')
+ consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
end
def tag_names
request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request)
- consume_refs_response(response, prefix: 'refs/tags/')
+ consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
end
def find_ref_name(commit_id, ref_prefix)
@@ -31,7 +34,7 @@ module Gitlab
commit_id: commit_id,
prefix: ref_prefix
)
- GitalyClient.call(@storage, :ref, :find_ref_name, request).name
+ encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup)
end
def count_tag_names
@@ -51,20 +54,28 @@ module Gitlab
private
- def consume_refs_response(response, prefix:)
- response.flat_map do |r|
- r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') }
- end
+ def consume_refs_response(response)
+ response.flat_map { |message| message.names.map { |name| yield(name) } }
end
def sort_by_param(sort_by)
+ sort_by = 'name' if sort_by == 'name_asc'
+
enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
enum_value
end
def consume_branches_response(response)
- response.flat_map { |r| r.branches }
+ response.flat_map do |message|
+ message.branches.map do |gitaly_branch|
+ Gitlab::Git::Branch.new(
+ @repository,
+ encode!(gitaly_branch.name.dup),
+ gitaly_branch.commit_id
+ )
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 319633656ff..2d1ae6a5925 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -2,11 +2,14 @@
module Gitlab
module GonHelper
+ include WebpackHelper
+
def add_gon_variables
gon.api_version = 'v4'
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
+ gon.webpack_public_path = webpack_public_path
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index e78b7f22e03..70da4080cae 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -52,7 +52,7 @@ module Gitlab
]
end
rescue RuntimeError => ex
- Rails.logger("unexpected error #{ex} when checking #{ok_metric}")
+ Rails.logger.error("unexpected error #{ex} when checking #{ok_metric}")
[metric(ok_metric, 0, **labels)]
end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index db7cdf4b5c7..f3d489aad0d 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -12,7 +12,8 @@ module Gitlab
'zh_HK' => '繁體中文(香港)',
'zh_TW' => '繁體中文(臺灣)',
'bg' => 'български',
- 'eo' => 'Esperanto'
+ 'eo' => 'Esperanto',
+ 'it' => 'Italiano'
}.freeze
def available_locales
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 1860352c96d..c8ad3a7a5e0 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -27,6 +27,7 @@ project_tree:
- :author
- :events
- merge_request_diff:
+ - :merge_request_diff_commits
- :merge_request_diff_files
- :events
- :timelogs
diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb
new file mode 100644
index 00000000000..977c05910d3
--- /dev/null
+++ b/lib/gitlab/issuable_metadata.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module IssuableMetadata
+ def issuable_meta_data(issuable_collection, collection_type)
+ # map has to be used here since using pluck or select will
+ # throw an error when ordering issuables by priority which inserts
+ # a new order into the collection.
+ # We cannot use reorder to not mess up the paginated collection.
+ issuable_ids = issuable_collection.map(&:id)
+
+ return {} if issuable_ids.empty?
+
+ issuable_note_count = ::Note.count_for_collection(issuable_ids, collection_type)
+ issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type)
+ issuable_merge_requests_count =
+ if collection_type == 'Issue'
+ ::MergeRequestsClosingIssues.count_for_collection(issuable_ids)
+ else
+ []
+ end
+
+ issuable_ids.each_with_object({}) do |id, issuable_meta|
+ downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
+ upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
+ notes = issuable_note_count.find { |notes| notes.noteable_id == id }
+ merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
+
+ issuable_meta[id] = ::Issuable::IssuableMeta.new(
+ upvotes.try(:count).to_i,
+ downvotes.try(:count).to_i,
+ notes.try(:count).to_i,
+ merge_requests.try(:last).to_i
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index c56c1a4322f..cdbdfa10d0e 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -76,5 +76,44 @@ module Gitlab
url.to_s
end
+
+ def to_kubeconfig(url:, namespace:, token:, ca_pem: nil)
+ config = {
+ apiVersion: 'v1',
+ clusters: [
+ name: 'gitlab-deploy',
+ cluster: {
+ server: url
+ }
+ ],
+ contexts: [
+ name: 'gitlab-deploy',
+ context: {
+ cluster: 'gitlab-deploy',
+ namespace: namespace,
+ user: 'gitlab-deploy'
+ }
+ ],
+ 'current-context': 'gitlab-deploy',
+ kind: 'Config',
+ users: [
+ {
+ name: 'gitlab-deploy',
+ user: { token: token }
+ }
+ ]
+ }
+
+ kubeconfig_embed_ca_pem(config, ca_pem) if ca_pem
+
+ config.deep_stringify_keys
+ end
+
+ private
+
+ def kubeconfig_embed_ca_pem(config, ca_pem)
+ cluster = config.dig(:clusters, 0, :cluster)
+ cluster[:'certificate-authority-data'] = Base64.encode64(ca_pem)
+ end
end
end
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 8779577258b..fb68627dedf 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -16,7 +16,7 @@ module Gitlab
def self.allowed?(user)
self.open(user) do |access|
if access.allowed?
- Users::UpdateService.new(user, last_credential_check_a: Time.now).execute
+ Users::UpdateService.new(user, last_credential_check_at: Time.now).execute
true
else
diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb
new file mode 100644
index 00000000000..219accfc029
--- /dev/null
+++ b/lib/gitlab/metrics/base_sampler.rb
@@ -0,0 +1,94 @@
+require 'logger'
+module Gitlab
+ module Metrics
+ class BaseSampler
+ def self.initialize_instance(*args)
+ raise "#{name} singleton instance already initialized" if @instance
+ @instance = new(*args)
+ at_exit(&@instance.method(:stop))
+ @instance
+ end
+
+ def self.instance
+ @instance
+ end
+
+ attr_reader :running
+
+ # interval - The sampling interval in seconds.
+ def initialize(interval)
+ interval_half = interval.to_f / 2
+
+ @interval = interval
+ @interval_steps = (-interval_half..interval_half).step(0.1).to_a
+
+ @mutex = Mutex.new
+ end
+
+ def enabled?
+ true
+ end
+
+ def start
+ return unless enabled?
+
+ @mutex.synchronize do
+ return if running
+ @running = true
+
+ @thread = Thread.new do
+ sleep(sleep_interval)
+
+ while running
+ safe_sample
+
+ sleep(sleep_interval)
+ end
+ end
+ end
+ end
+
+ def stop
+ @mutex.synchronize do
+ return unless running
+
+ @running = false
+
+ if @thread
+ @thread.wakeup if @thread.alive?
+ @thread.join
+ @thread = nil
+ end
+ end
+ end
+
+ def safe_sample
+ sample
+ rescue => e
+ Rails.logger.warn("#{self.class}: #{e}, stopping")
+ stop
+ end
+
+ def sample
+ raise NotImplementedError
+ end
+
+ # Returns the sleep interval with a random adjustment.
+ #
+ # The random adjustment is put in place to ensure we:
+ #
+ # 1. Don't generate samples at the exact same interval every time (thus
+ # potentially missing anything that happens in between samples).
+ # 2. Don't sample data at the same interval two times in a row.
+ def sleep_interval
+ while step = @interval_steps.sample
+ if step != @last_step
+ @last_step = step
+
+ return @interval + @last_step
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb
new file mode 100644
index 00000000000..b3da360be8f
--- /dev/null
+++ b/lib/gitlab/metrics/connection_rack_middleware.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ module Metrics
+ class ConnectionRackMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def self.rack_request_count
+ @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
+ end
+
+ def self.rack_response_count
+ @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
+ end
+
+ def self.rack_uncaught_errors_count
+ @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
+ end
+
+ def self.rack_execution_time
+ @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
+ {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
+ end
+
+ def call(env)
+ method = env['REQUEST_METHOD'].downcase
+ started = Time.now.to_f
+ begin
+ ConnectionRackMiddleware.rack_request_count.increment(method: method)
+
+ status, headers, body = @app.call(env)
+
+ ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
+ [status, headers, body]
+ rescue
+ ConnectionRackMiddleware.rack_uncaught_errors_count.increment
+ raise
+ ensure
+ elapsed = Time.now.to_f - started
+ ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/influx_sampler.rb
index 0000450d9bb..6db1dd755b7 100644
--- a/lib/gitlab/metrics/sampler.rb
+++ b/lib/gitlab/metrics/influx_sampler.rb
@@ -5,14 +5,11 @@ module Gitlab
# This class is used to gather statistics that can't be directly associated
# with a transaction such as system memory usage, garbage collection
# statistics, etc.
- class Sampler
+ class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds.
def initialize(interval = Metrics.settings[:sample_interval])
- interval_half = interval.to_f / 2
-
- @interval = interval
- @interval_steps = (-interval_half..interval_half).step(0.1).to_a
- @last_step = nil
+ super(interval)
+ @last_step = nil
@metrics = []
@@ -26,18 +23,6 @@ module Gitlab
end
end
- def start
- Thread.new do
- Thread.current.abort_on_exception = true
-
- loop do
- sleep(sleep_interval)
-
- sample
- end
- end
- end
-
def sample
sample_memory_usage
sample_file_descriptors
@@ -86,7 +71,7 @@ module Gitlab
end
def sample_gc
- time = GC::Profiler.total_time * 1000.0
+ time = GC::Profiler.total_time * 1000.0
stats = GC.stat.merge(total_time: time)
# We want the difference of GC runs compared to the last sample, not the
@@ -111,23 +96,6 @@ module Gitlab
def sidekiq?
Sidekiq.server?
end
-
- # Returns the sleep interval with a random adjustment.
- #
- # The random adjustment is put in place to ensure we:
- #
- # 1. Don't generate samples at the exact same interval every time (thus
- # potentially missing anything that happens in between samples).
- # 2. Don't sample data at the same interval two times in a row.
- def sleep_interval
- while step = @interval_steps.sample
- if step != @last_step
- @last_step = step
-
- return @interval + @last_step
- end
- end
- end
end
end
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 9d314a56e58..fb7bbc7cfc7 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -29,8 +29,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels)
end
- def gauge(name, docstring, base_labels = {})
- provide_metric(name) || registry.gauge(name, docstring, base_labels)
+ def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
+ provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
diff --git a/lib/gitlab/metrics/unicorn_sampler.rb b/lib/gitlab/metrics/unicorn_sampler.rb
new file mode 100644
index 00000000000..f6987252039
--- /dev/null
+++ b/lib/gitlab/metrics/unicorn_sampler.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Metrics
+ class UnicornSampler < BaseSampler
+ def initialize(interval)
+ super(interval)
+ end
+
+ def unicorn_active_connections
+ @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
+ end
+
+ def unicorn_queued_connections
+ @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ end
+
+ def enabled?
+ # Raindrops::Linux.tcp_listener_stats is only present on Linux
+ unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
+ end
+
+ def sample
+ Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
+ end
+
+ Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
+ end
+ end
+
+ private
+
+ def tcp_listeners
+ @tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
+ end
+
+ def unix_listeners
+ @unix_listeners ||= Unicorn.listener_names - tcp_listeners
+ end
+
+ def unicorn_with_listeners?
+ defined?(Unicorn) && Unicorn.listener_names.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/otp_key_rotator.rb b/lib/gitlab/otp_key_rotator.rb
index 0d541935bc6..22332474945 100644
--- a/lib/gitlab/otp_key_rotator.rb
+++ b/lib/gitlab/otp_key_rotator.rb
@@ -34,7 +34,7 @@ module Gitlab
write_csv do |csv|
ActiveRecord::Base.transaction do
- User.with_two_factor.in_batches do |relation|
+ User.with_two_factor.in_batches do |relation| # rubocop: disable Cop/InBatches
rows = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt)
rows.each do |row|
user = %i[id ciphertext iv salt].zip(row).to_h
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 10eb99fb461..d81f825ef96 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -112,6 +112,7 @@ module Gitlab
# this group would not be accessible through `/groups/parent/activity` since
# this would map to the activity-page of its parent.
GROUP_ROUTES = %w[
+ -
activity
analytics
audit_events
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index 163a40ad306..2da2ce45ebc 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -1,7 +1,33 @@
module Gitlab
module PerformanceBar
- def self.enabled?
- Feature.enabled?('gitlab_performance_bar')
+ include Gitlab::CurrentSettings
+
+ ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids'.freeze
+
+ def self.enabled?(user = nil)
+ return false unless user && allowed_group_id
+
+ allowed_user_ids.include?(user.id)
+ end
+
+ def self.allowed_group_id
+ current_application_settings.performance_bar_allowed_group_id
+ end
+
+ def self.allowed_user_ids
+ Rails.cache.fetch(ALLOWED_USER_IDS_KEY) do
+ group = Group.find_by_id(allowed_group_id)
+
+ if group
+ GroupMembersFinder.new(group).execute.pluck(:user_id)
+ else
+ []
+ end
+ end
+ end
+
+ def self.expire_allowed_user_ids_cache
+ Rails.cache.delete(ALLOWED_USER_IDS_KEY)
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index b706434217d..c1ee20b6977 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -19,14 +19,6 @@ module Gitlab
"It must start with letter, digit, emoji or '_'."
end
- def file_name_regex
- @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
- end
-
- def file_name_regex_message
- "can contain only letters, digits, '_', '-', '@', '+' and '.'."
- end
-
def container_registry_reference_regex
Gitlab::PathRegex.git_reference_regex
end
@@ -38,8 +30,12 @@ module Gitlab
@container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z}
end
+ def environment_name_regex_chars
+ 'a-zA-Z0-9_/\\$\\{\\}\\. -'
+ end
+
def environment_name_regex
- @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze
+ @environment_name_regex ||= /\A[#{environment_name_regex_chars}]+\z/.freeze
end
def environment_name_regex_message
diff --git a/lib/gitlab/routes/legacy_builds.rb b/lib/gitlab/routes/legacy_builds.rb
deleted file mode 100644
index 36d1a8a6f64..00000000000
--- a/lib/gitlab/routes/legacy_builds.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Gitlab
- module Routes
- class LegacyBuilds
- def initialize(map)
- @map = map
- end
-
- def draw
- @map.instance_eval do
- resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
- collection do
- resources :artifacts, only: [], controller: 'build_artifacts' do
- collection do
- get :latest_succeeded,
- path: '*ref_name_and_path',
- format: false
- end
- end
- end
-
- member do
- get :raw
- end
-
- resource :artifacts, only: [], controller: 'build_artifacts' do
- get :download
- get :browse, path: 'browse(/*path)', format: false
- get :file, path: 'file/*path', format: false
- get :raw, path: 'raw/*path', format: false
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 632e2d87500..e57890f1143 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -2,10 +2,35 @@ module Gitlab
module Routing
extend ActiveSupport::Concern
+ mattr_accessor :_includers
+ self._includers = []
+
included do
+ Gitlab::Routing.includes_helpers(self)
+
include Gitlab::Routing.url_helpers
end
+ def self.includes_helpers(klass)
+ self._includers << klass
+ end
+
+ def self.add_helpers(mod)
+ url_helpers.include mod
+ url_helpers.extend mod
+
+ GitlabRoutingHelper.include mod
+ GitlabRoutingHelper.extend mod
+
+ app_url_helpers = Gitlab::Application.routes.named_routes.url_helpers_module
+ app_url_helpers.include mod
+ app_url_helpers.extend mod
+
+ _includers.each do |klass|
+ klass.include mod
+ end
+ end
+
# Returns the URL helpers Module.
#
# This method caches the output as Rails' "url_helpers" method creates an
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 22554236c38..0baea092e6a 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -2,6 +2,8 @@ require 'securerandom'
module Gitlab
class Shell
+ GITLAB_SHELL_ENV_VARS = %w(GIT_TERMINAL_PROMPT).freeze
+
Error = Class.new(StandardError)
KeyAdder = Struct.new(:io) do
@@ -67,8 +69,8 @@ module Gitlab
# add_repository("/path/to/storage", "gitlab/gitlab-ci")
#
def add_repository(storage, name)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path,
- 'add-project', storage, "#{name}.git"])
+ gitlab_shell_fast_execute([gitlab_shell_projects_path,
+ 'add-project', storage, "#{name}.git"])
end
# Import repository
@@ -82,10 +84,9 @@ module Gitlab
def import_repository(storage, name, url)
# Timeout should be less than 900 ideally, to prevent the memory killer
# to silently kill the process without knowing we are timing out here.
- output, status = Popen.popen([gitlab_shell_projects_path, 'import-project',
- storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"])
- raise Error, output unless status.zero?
- true
+ cmd = [gitlab_shell_projects_path, 'import-project',
+ storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"]
+ gitlab_shell_fast_execute_raise_error(cmd)
end
# Fetch remote for repository
@@ -103,9 +104,7 @@ module Gitlab
args << '--force' if forced
args << '--no-tags' if no_tags
- output, status = Popen.popen(args)
- raise Error, output unless status.zero?
- true
+ gitlab_shell_fast_execute_raise_error(args)
end
# Move repository
@@ -117,8 +116,8 @@ module Gitlab
# mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new")
#
def mv_repository(storage, path, new_path)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
- storage, "#{path}.git", "#{new_path}.git"])
+ gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project',
+ storage, "#{path}.git", "#{new_path}.git"])
end
# Fork repository to new namespace
@@ -131,9 +130,9 @@ module Gitlab
# fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
#
def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
- forked_from_storage, "#{path}.git", forked_to_storage,
- fork_namespace])
+ gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project',
+ forked_from_storage, "#{path}.git", forked_to_storage,
+ fork_namespace])
end
# Remove repository from file system
@@ -145,8 +144,8 @@ module Gitlab
# remove_repository("/path/to/storage", "gitlab/gitlab-ci")
#
def remove_repository(storage, name)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path,
- 'rm-project', storage, "#{name}.git"])
+ gitlab_shell_fast_execute([gitlab_shell_projects_path,
+ 'rm-project', storage, "#{name}.git"])
end
# Add new key to gitlab-shell
@@ -155,8 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- Gitlab::Utils.system_silent([gitlab_shell_keys_path,
- 'add-key', key_id, self.class.strip_key(key_content)])
+ gitlab_shell_fast_execute([gitlab_shell_keys_path,
+ 'add-key', key_id, self.class.strip_key(key_content)])
end
# Batch-add keys to authorized_keys
@@ -175,8 +174,10 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- Gitlab::Utils.system_silent([gitlab_shell_keys_path,
- 'rm-key', key_id, key_content])
+ args = [gitlab_shell_keys_path, 'rm-key', key_id]
+ args << key_content if key_content
+
+ gitlab_shell_fast_execute(args)
end
# Remove all ssh keys from gitlab shell
@@ -185,7 +186,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
+ gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
end
# Add empty directory for storing repositories
@@ -267,5 +268,31 @@ module Gitlab
def gitlab_shell_keys_path
File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
end
+
+ private
+
+ def gitlab_shell_fast_execute(cmd)
+ output, status = gitlab_shell_fast_execute_helper(cmd)
+
+ return true if status.zero?
+
+ Rails.logger.error("gitlab-shell failed with error #{status}: #{output}")
+ false
+ end
+
+ def gitlab_shell_fast_execute_raise_error(cmd)
+ output, status = gitlab_shell_fast_execute_helper(cmd)
+
+ raise Error, output unless status.zero?
+ true
+ end
+
+ def gitlab_shell_fast_execute_helper(cmd)
+ vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS)
+
+ # Don't pass along the entire parent environment to prevent gitlab-shell
+ # from wasting I/O by searching through GEM_PATH
+ Bundler.with_original_env { Popen.popen(cmd, nil, vars) }
+ end
end
end
diff --git a/lib/gitlab/slash_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb
index 27696436574..e13808a2720 100644
--- a/lib/gitlab/slash_commands/presenters/base.rb
+++ b/lib/gitlab/slash_commands/presenters/base.rb
@@ -2,7 +2,7 @@ module Gitlab
module SlashCommands
module Presenters
class Base
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
def initialize(resource = nil)
@resource = resource
diff --git a/lib/gitlab/sql/glob.rb b/lib/gitlab/sql/glob.rb
new file mode 100644
index 00000000000..5e89e12b2b1
--- /dev/null
+++ b/lib/gitlab/sql/glob.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module SQL
+ module Glob
+ extend self
+
+ # Convert a simple glob pattern with wildcard (*) to SQL LIKE pattern
+ # with SQL expression
+ def to_like(pattern)
+ <<~SQL
+ REPLACE(REPLACE(REPLACE(#{pattern},
+ #{q('%')}, #{q('\\%')}),
+ #{q('_')}, #{q('\\_')}),
+ #{q('*')}, #{q('%')})
+ SQL
+ end
+
+ def q(string)
+ ActiveRecord::Base.connection.quote(string)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 23af9318d1a..35792d2d67f 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,6 +1,6 @@
module Gitlab
class UrlBuilder
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include GitlabRoutingHelper
include ActionView::RecordIdentifier
@@ -23,9 +23,9 @@ module Gitlab
when WikiPage
wiki_page_url
when ProjectSnippet
- project_snippet_url(object)
+ project_snippet_url(object.project, object)
when Snippet
- personal_snippet_url(object)
+ snippet_url(object)
else
raise NotImplementedError.new("No URL builder defined for #{object.class}")
end
@@ -65,13 +65,13 @@ module Gitlab
if snippet.is_a?(PersonalSnippet)
snippet_url(snippet, anchor: dom_id(object))
else
- project_snippet_url(snippet, anchor: dom_id(object))
+ project_snippet_url(snippet.project, snippet, anchor: dom_id(object))
end
end
end
def wiki_page_url
- namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
+ project_wiki_url(object.wiki.project, object.slug)
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 38dc82493cf..f19b325a126 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -20,7 +20,8 @@ module Gitlab
counts: {
boards: Board.count,
ci_builds: ::Ci::Build.count,
- ci_pipelines: ::Ci::Pipeline.count,
+ ci_internal_pipelines: ::Ci::Pipeline.internal.count,
+ ci_external_pipelines: ::Ci::Pipeline.external.count,
ci_runners: ::Ci::Runner.count,
ci_triggers: ::Ci::Trigger.count,
ci_pipeline_schedules: ::Ci::PipelineSchedule.count,
diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb
index dbfe0941e4d..841fb681435 100644
--- a/lib/gitlab/view/presenter/base.rb
+++ b/lib/gitlab/view/presenter/base.rb
@@ -15,6 +15,11 @@ module Gitlab
super(user, action, overriden_subject || subject)
end
+ # delegate all #can? queries to the subject
+ def declarative_policy_delegate
+ subject
+ end
+
class_methods do
def presenter?
true
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index f96ee69096d..4aef23b6aee 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -25,27 +25,25 @@ module Gitlab
RepoPath: repo_path
}
- if Gitlab.config.gitaly.enabled
- server = {
- address: Gitlab::GitalyClient.address(project.repository_storage),
- token: Gitlab::GitalyClient.token(project.repository_storage)
- }
- params[:Repository] = repository.gitaly_repository.to_h
-
- feature_enabled = case action.to_s
- when 'git_receive_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
- when 'git_upload_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
- when 'info_refs'
- true
- else
- raise "Unsupported action: #{action}"
- end
- if feature_enabled
- params[:GitalyAddress] = server[:address] # This field will be deprecated
- params[:GitalyServer] = server
- end
+ server = {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ }
+ params[:Repository] = repository.gitaly_repository.to_h
+
+ feature_enabled = case action.to_s
+ when 'git_receive_pack'
+ Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
+ when 'git_upload_pack'
+ Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
+ when 'info_refs'
+ true
+ else
+ raise "Unsupported action: #{action}"
+ end
+ if feature_enabled
+ params[:GitalyAddress] = server[:address] # This field will be deprecated
+ params[:GitalyServer] = server
end
params
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
index 99f9c2c9b04..7cfe76b7b71 100644
--- a/lib/peek/rblineprof/custom_controller_helpers.rb
+++ b/lib/peek/rblineprof/custom_controller_helpers.rb
@@ -41,9 +41,14 @@ module Peek
]
end.sort_by{ |a,b,c,d,e,f| -f }
- output = ''
- per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
+ output = "<div class='modal-dialog modal-full'><div class='modal-content'>"
+ output << "<div class='modal-header'>"
+ output << "<button class='close btn btn-link btn-sm' type='button' data-dismiss='modal'>X</button>"
+ output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
+ output << "</div>"
+ output << "<div class='modal-body'>"
+ per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
output << "<div class='peek-rblineprof-file'><div class='heading'>"
show_src = file_sort > min
@@ -86,11 +91,32 @@ module Peek
output << "</div></div>" # .data then .peek-rblineprof-file
end
- response.body += "<div class='peek-rblineprof-modal' id='line-profile'>#{output}</div>".html_safe
+ output << "</div></div></div>"
+
+ response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output}</div>".html_safe
end
ret
end
+
+ private
+
+ def human_description(lineprofiler_param)
+ case lineprofiler_param
+ when 'app'
+ 'app/ & lib/'
+ when 'views'
+ 'app/view/'
+ when 'gems'
+ 'vendor/gems'
+ when 'all'
+ 'everything in Rails.root'
+ when 'stdlib'
+ 'everything in the Ruby standard library'
+ else
+ 'app/, config/, lib/, vendor/ & plugin/'
+ end
+ end
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index e3883278886..e9fb6a008b0 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -42,8 +42,7 @@ namespace :gitlab do
http_clone_url = project.http_url_to_repo
ssh_clone_url = project.ssh_url_to_repo
- omniauth_providers = Gitlab.config.omniauth.providers
- omniauth_providers.map! { |provider| provider['name'] }
+ omniauth_providers = Gitlab.config.omniauth.providers.map { |provider| provider['name'] }
puts ""
puts "GitLab information".color(:yellow)
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 370aca1f1d9..db4dc9a02da 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -1,19 +1,31 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
# Lyubomir Vasilev <lyubomirv@abv.bg>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-12 19:29-0500\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-13 04:23-0400\n"
+"PO-Revision-Date: 2017-06-23 04:07-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n"
"Language: bg\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "%d подаване беше пропуснато, за да не се натоварва системата."
+msgstr[1] "%d подавания бяха пропуснати, за да не се натоварва системата."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d подаване"
+msgstr[1] "%d подавания"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} подаде %{commit_timeago}"
@@ -66,9 +78,24 @@ msgstr ""
"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте "
"промените си. %{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Търсете в клоновете"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Превключване на клона"
+
msgid "Branches"
msgstr "Клонове"
+msgid "Browse Directory"
+msgstr "Преглед на папката"
+
+msgid "Browse File"
+msgstr "Преглед на файла"
+
+msgid "Browse Files"
+msgstr "Преглед на файловете"
+
msgid "Browse files"
msgstr "Разглеждане на файловете"
@@ -90,11 +117,8 @@ msgstr "Отмяна в клона"
msgid "ChangeTypeAction|Cherry-pick"
msgstr "Подбиране"
-msgid "ChangeType|commit"
-msgstr "подаване"
-
-msgid "ChangeType|merge request"
-msgstr "заявка за сливане"
+msgid "ChangeTypeAction|Revert"
+msgstr "Отмяна"
msgid "Changelog"
msgstr "Списък с промени"
@@ -105,7 +129,7 @@ msgstr "Графики"
msgid "Cherry-pick this commit"
msgstr "Подбиране на това подаване"
-msgid "Cherry-pick this merge-request"
+msgid "Cherry-pick this merge request"
msgstr "Подбиране на тази заявка за сливане"
msgid "CiStatusLabel|canceled"
@@ -170,12 +194,18 @@ msgstr[1] "Подавания"
msgid "Commit message"
msgstr "Съобщение за подаването"
+msgid "CommitBoxTitle|Commit"
+msgstr "Подаване"
+
msgid "CommitMessage|Add %{file_name}"
msgstr "Добавяне на „%{file_name}“"
msgid "Commits"
msgstr "Подавания"
+msgid "Commits feed"
+msgstr "Поток от подавания"
+
msgid "Commits|History"
msgstr "История"
@@ -224,9 +254,6 @@ msgstr "Часова зона за „Cron“"
msgid "Cron syntax"
msgstr "Синтаксис на „Cron“"
-msgid "Custom"
-msgstr "Персонализиран"
-
msgid "Custom notification events"
msgstr "Персонализирани събития за известяване"
@@ -341,6 +368,9 @@ msgstr "Планът за схема не може да бъде премахн
msgid "Files"
msgstr "Файлове"
+msgid "Filter by commit message"
+msgstr "Филтриране по съобщение"
+
msgid "Find by path"
msgstr "Търсене по път"
@@ -412,6 +442,9 @@ msgstr "Последно подаване"
msgid "Learn more in the"
msgstr "Научете повече в"
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "документацията относно планирането на схеми"
+
msgid "Leave group"
msgstr "Напускане на групата"
@@ -578,6 +611,15 @@ msgstr "Поемане на собствеността"
msgid "PipelineSchedules|Target"
msgstr "Цел"
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "собствен"
+
+msgid "Pipeline|with stage"
+msgstr "с етап"
+
+msgid "Pipeline|with stages"
+msgstr "с етапи"
+
msgid "Project '%{project_name}' queued for deletion."
msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване."
@@ -677,7 +719,7 @@ msgstr "Заявка за достъп"
msgid "Revert this commit"
msgstr "Отмяна на това подаване"
-msgid "Revert this merge-request"
+msgid "Revert this merge request"
msgstr "Отмяна на тази заявка за сливане"
msgid "Save pipeline schedule"
@@ -690,7 +732,7 @@ msgid "Scheduling Pipelines"
msgstr "Планиране на схемите"
msgid "Search branches and tags"
-msgstr "Търсене в клоновете и етикетите"
+msgstr "Търсете в клоновете и етикетите"
msgid "Select Archive Format"
msgstr "Изберете формата на архива"
@@ -701,10 +743,10 @@ msgstr "Изберете часова зона"
msgid "Select target branch"
msgstr "Изберете целеви клон"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
"Задайте парола на профила си, за да можете да изтегляте и изпращате промени "
-"чрез %{protocol}"
+"чрез %{protocol}."
msgid "Set up CI"
msgstr "Настройка на НИ"
@@ -729,8 +771,8 @@ msgstr "Изходен код"
msgid "StarProject|Star"
msgstr "Звезда"
-msgid "Start a <strong>new merge request</strong> with these changes"
-msgstr "Създайте <strong>нова заявка за сливане</strong> с тези промени"
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Създайте %{new_merge_request} с тези промени"
msgid "Switch branch/tag"
msgstr "Преминаване към клон/етикет"
@@ -779,7 +821,7 @@ msgid ""
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
-"Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени "
+"Планът за схемата ще изпълнява схемите в бъдеще, периодично, за определени "
"клонове или етикети. Тези планирани схеми ще наследят ограниченията на "
"достъпа до проекта на свързания с тях потребител."
@@ -1022,9 +1064,15 @@ msgstr "Качване на нов файл"
msgid "Upload file"
msgstr "Качване на файл"
+msgid "UploadLink|click to upload"
+msgstr "щракнете за качване"
+
msgid "Use your global notification setting"
msgstr "Използване на глобалната Ви настройка за известията"
+msgid "View open merge request"
+msgstr "Преглед на отворената заявка за сливане"
+
msgid "VisibilityLevel|Internal"
msgstr "Вътрешен"
@@ -1069,6 +1117,9 @@ msgstr ""
msgid "You can only add files when you are on a branch"
msgstr "Можете да добавяте файлове само когато се намирате в клон"
+msgid "You have reached your project limit"
+msgstr "Не можете да създавате повече проекти"
+
msgid "You must sign in to star a project"
msgstr "Трябва да се впишете, за да отбележите проект със звезда"
@@ -1115,6 +1166,9 @@ msgid_plural "days"
msgstr[0] "ден"
msgstr[1] "дни"
+msgid "new merge request"
+msgstr "нова заявка за сливане"
+
msgid "notification emails"
msgstr "известия по е-поща"
@@ -1123,11 +1177,3 @@ msgid_plural "parents"
msgstr[0] "родител"
msgstr[1] "родители"
-msgid "pipeline schedules documentation"
-msgstr "документацията за планирането на схеми"
-
-msgid "with stage"
-msgid_plural "with stages"
-msgstr[0] "с етап"
-msgstr[1] "с етапи"
-
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index afb8fb3176f..7065b7a635f 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -17,9 +17,27 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n"
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
msgid "About auto deploy"
msgstr ""
@@ -61,9 +79,24 @@ msgstr[1] ""
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr ""
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
msgid "Branches"
msgstr ""
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
msgid "Browse files"
msgstr ""
@@ -159,6 +192,9 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
msgid "Commit message"
msgstr ""
@@ -171,6 +207,9 @@ msgstr ""
msgid "Commits"
msgstr ""
+msgid "Commits feed"
+msgstr ""
+
msgid "Commits|History"
msgstr ""
@@ -195,6 +234,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
msgid "Create directory"
msgstr ""
@@ -213,6 +255,9 @@ msgstr ""
msgid "CreateTag|Tag"
msgstr ""
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
msgid "Cron Timezone"
msgstr ""
@@ -323,6 +368,9 @@ msgstr ""
msgid "Files"
msgstr ""
+msgid "Filter by commit message"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -370,6 +418,15 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
+msgid "Jobs for last month"
+msgstr ""
+
+msgid "Jobs for last week"
+msgstr ""
+
+msgid "Jobs for last year"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -535,6 +592,21 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
msgid "PipelineSchedules|Activated"
msgstr ""
@@ -547,6 +619,12 @@ msgstr ""
msgid "PipelineSchedules|Inactive"
msgstr ""
+msgid "PipelineSchedules|Input variable key"
+msgstr ""
+
+msgid "PipelineSchedules|Input variable value"
+msgstr ""
+
msgid "PipelineSchedules|Next Run"
msgstr ""
@@ -556,15 +634,33 @@ msgstr ""
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
+msgid "PipelineSchedules|Remove variable row"
+msgstr ""
+
msgid "PipelineSchedules|Take ownership"
msgstr ""
msgid "PipelineSchedules|Target"
msgstr ""
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -688,7 +784,7 @@ msgstr ""
msgid "Select target branch"
msgstr ""
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set up CI"
@@ -714,10 +810,7 @@ msgstr ""
msgid "StarProject|Star"
msgstr ""
-msgid "Start a %{new_merge_request} with these changes"
-msgstr ""
-
-msgid "Start a <strong>new merge request</strong> with these changes"
+msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Switch branch/tag"
@@ -948,9 +1041,15 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "UploadLink|click to upload"
+msgstr ""
+
msgid "Use your global notification setting"
msgstr ""
+msgid "View open merge request"
+msgstr ""
+
msgid "VisibilityLevel|Internal"
msgstr ""
@@ -970,6 +1069,12 @@ msgid "Withdraw Access Request"
msgstr ""
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 3ef9e19067d..0ca8dfca266 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -4,17 +4,28 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-20 06:24-0400\n"
+"PO-Revision-Date: 2017-07-05 02:56-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
"Language: eo\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "%d enmetado estis transsaltita, por ne troŝarĝi la sistemon."
+msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d enmetado"
+msgstr[1] "%d enmetadoj"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} enmetis %{commit_timeago}"
@@ -67,9 +78,24 @@ msgstr ""
"disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn "
"ŝanĝojn. %{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Serĉu branĉon"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Iri al branĉo"
+
msgid "Branches"
msgstr "Branĉoj"
+msgid "Browse Directory"
+msgstr "Foliumi dosierujon"
+
+msgid "Browse File"
+msgstr "Foliumi dosieron"
+
+msgid "Browse Files"
+msgstr "Foliumi dosierojn"
+
msgid "Browse files"
msgstr "Elekti dosierojn"
@@ -177,6 +203,9 @@ msgstr "Aldoni „%{file_name}“"
msgid "Commits"
msgstr "Enmetadoj"
+msgid "Commits feed"
+msgstr "Fluo de enmetadoj"
+
msgid "Commits|History"
msgstr "Historio"
@@ -339,6 +368,9 @@ msgstr "Ne eblas forigi la ĉenstablan planon"
msgid "Files"
msgstr "Dosieroj"
+msgid "Filter by commit message"
+msgstr "Filtri per mesaĝo"
+
msgid "Find by path"
msgstr "Trovi per dosierindiko"
@@ -712,10 +744,10 @@ msgstr "Elektu horzonon"
msgid "Select target branch"
msgstr "Elektu celan branĉon"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
"Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per "
-"%{protocol}"
+"%{protocol}."
msgid "Set up CI"
msgstr "Agordi SI"
@@ -1033,9 +1065,15 @@ msgstr "Alŝuti novan dosieron"
msgid "Upload file"
msgstr "Alŝuti dosieron"
+msgid "UploadLink|click to upload"
+msgstr "alklaku por alŝuti"
+
msgid "Use your global notification setting"
msgstr "Uzi vian ĝeneralan agordon pri la sciigoj"
+msgid "View open merge request"
+msgstr "Vidi la malfermitan peton pri kunfando"
+
msgid "VisibilityLevel|Internal"
msgstr "Interna"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index cc44a06cbc5..cec086b871c 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -719,8 +719,8 @@ msgstr "Selecciona una zona horaria"
msgid "Select target branch"
msgstr "Selecciona una rama de destino"
-msgid "Set a password on your account to pull or push via %{protocol}"
-msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
msgid "Set up CI"
msgstr "Configurar CI"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 07f9efeb495..6066314b32e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-19 15:50-0500\n"
-"PO-Revision-Date: 2017-06-19 15:50-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"PO-Revision-Date: 2017-07-05 08:50-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-msgid "%d additional commit have been omitted to prevent performance issues."
+msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
@@ -31,6 +31,14 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
msgid "About auto deploy"
msgstr ""
@@ -185,6 +193,9 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
msgid "Commit message"
msgstr ""
@@ -224,6 +235,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
msgid "Create directory"
msgstr ""
@@ -242,6 +256,9 @@ msgstr ""
msgid "CreateTag|Tag"
msgstr ""
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
msgid "Cron Timezone"
msgstr ""
@@ -402,6 +419,15 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
+msgid "Jobs for last month"
+msgstr ""
+
+msgid "Jobs for last week"
+msgstr ""
+
+msgid "Jobs for last year"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -567,6 +593,21 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
msgid "PipelineSchedules|Activated"
msgstr ""
@@ -579,6 +620,12 @@ msgstr ""
msgid "PipelineSchedules|Inactive"
msgstr ""
+msgid "PipelineSchedules|Input variable key"
+msgstr ""
+
+msgid "PipelineSchedules|Input variable value"
+msgstr ""
+
msgid "PipelineSchedules|Next Run"
msgstr ""
@@ -588,15 +635,33 @@ msgstr ""
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
+msgid "PipelineSchedules|Remove variable row"
+msgstr ""
+
msgid "PipelineSchedules|Take ownership"
msgstr ""
msgid "PipelineSchedules|Target"
msgstr ""
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -720,7 +785,7 @@ msgstr ""
msgid "Select target branch"
msgstr ""
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set up CI"
@@ -1005,6 +1070,12 @@ msgid "Withdraw Access Request"
msgstr ""
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
new file mode 100644
index 00000000000..db992021403
--- /dev/null
+++ b/locale/it/gitlab.po
@@ -0,0 +1,1185 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Paolo Falomo <info@paolofalomo.it>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-07-02 10:32-0400\n"
+"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
+"Language-Team: Italian\n"
+"Language: it\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli "
+"issues."
+msgstr[1] ""
+"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni "
+"negli issues."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d commit"
+msgstr[1] "%d commit"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} ha committato %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "Riguardo il rilascio automatico"
+
+msgid "Active"
+msgstr "Attivo"
+
+msgid "Activity"
+msgstr "Attività"
+
+msgid "Add Changelog"
+msgstr "Aggiungi Changelog"
+
+msgid "Add Contribution guide"
+msgstr "Aggiungi Guida per contribuire"
+
+msgid "Add License"
+msgstr "Aggiungi Licenza"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Aggiungi una chiave SSH al tuo profilo per eseguire pull o push tramite SSH"
+
+msgid "Add new directory"
+msgstr "Aggiungi una directory (cartella)"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Progetto archiviato! La Repository è sola-lettura"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Sei sicuro di voler cancellare questa pipeline programmata?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+"Aggiungi un file tramite trascina &amp; rilascia ( drag &amp; drop) o "
+"%{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branch"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un "
+"rilascio automatico scegli un template CI di Gitlab e committa le tue "
+"modifiche %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Cerca branches"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Cambia branch"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Naviga direttori"
+
+msgid "Browse File"
+msgstr "Esplora File"
+
+msgid "Browse Files"
+msgstr "Esplora Files"
+
+msgid "Browse files"
+msgstr "Guarda i files"
+
+msgid "ByAuthor|by"
+msgstr "per"
+
+msgid "CI configuration"
+msgstr "Configurazione CI (Integrazione Continua)"
+
+msgid "Cancel"
+msgstr "Cancella"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Preleva nella branch"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Ripristina nella branch"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Ripristina"
+
+msgid "Changelog"
+msgstr "Changelog"
+
+msgid "Charts"
+msgstr "Grafici"
+
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick this commit"
+
+msgid "Cherry-pick this merge request"
+msgstr "Cherry-pick questa richiesta di merge"
+
+msgid "CiStatusLabel|canceled"
+msgstr "cancellato"
+
+msgid "CiStatusLabel|created"
+msgstr "creato"
+
+msgid "CiStatusLabel|failed"
+msgstr "fallito"
+
+msgid "CiStatusLabel|manual action"
+msgstr "azione manuale"
+
+msgid "CiStatusLabel|passed"
+msgstr "superata"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "superata con avvisi"
+
+msgid "CiStatusLabel|pending"
+msgstr "in coda"
+
+msgid "CiStatusLabel|skipped"
+msgstr "saltata"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "in attesa di azione manuale"
+
+msgid "CiStatusText|blocked"
+msgstr "bloccata"
+
+msgid "CiStatusText|canceled"
+msgstr "cancellata"
+
+msgid "CiStatusText|created"
+msgstr "creata"
+
+msgid "CiStatusText|failed"
+msgstr "fallita"
+
+msgid "CiStatusText|manual"
+msgstr "manuale"
+
+msgid "CiStatusText|passed"
+msgstr "superata"
+
+msgid "CiStatusText|pending"
+msgstr "in coda"
+
+msgid "CiStatusText|skipped"
+msgstr "saltata"
+
+msgid "CiStatus|running"
+msgstr "in corso"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Commit"
+msgstr[1] "Commits"
+
+msgid "Commit message"
+msgstr "Messaggio del commit"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Commit"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Aggiungi %{file_name}"
+
+msgid "Commits"
+msgstr "Commits"
+
+msgid "Commits feed"
+msgstr "Feed dei Commits"
+
+msgid "Commits|History"
+msgstr "Cronologia"
+
+msgid "Committed by"
+msgstr "Committato da "
+
+msgid "Compare"
+msgstr "Confronta"
+
+msgid "Contribution guide"
+msgstr "Guida per contribuire"
+
+msgid "Contributors"
+msgstr "Collaboratori"
+
+msgid "Copy URL to clipboard"
+msgstr "Copia URL negli appunti"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copia l'SHA del commit negli appunti"
+
+msgid "Create New Directory"
+msgstr "Crea una nuova cartella"
+
+msgid "Create directory"
+msgstr "Crea cartella"
+
+msgid "Create empty bare repository"
+msgstr "Crea una repository vuota"
+
+msgid "Create merge request"
+msgstr "Crea una richiesta di merge"
+
+msgid "Create new..."
+msgstr "Crea nuovo..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fork"
+
+msgid "CreateTag|Tag"
+msgstr "Tag"
+
+msgid "Cron Timezone"
+msgstr "Cron Timezone"
+
+msgid "Cron syntax"
+msgstr "Sintassi Cron"
+
+msgid "Custom notification events"
+msgstr "Eventi-Notifica personalizzati"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"I livelli di notifica personalizzati sono uguali a quelli di partecipazione. "
+"Con i livelli di notifica personalizzati riceverai anche notifiche per gli "
+"eventi da te scelti %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Statistiche Cicliche"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea "
+"ed il rilascio in produzione del tuo progetto"
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Codice"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Issue"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Pianificazione"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Produzione"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Revisione"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Pre-rilascio"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Test"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Definisci un patter personalizzato mediante la sintassi cron"
+
+msgid "Delete"
+msgstr "Elimina"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Rilascio"
+msgstr[1] "Rilasci"
+
+msgid "Description"
+msgstr "Descrizione"
+
+msgid "Directory name"
+msgstr "Nome cartella"
+
+msgid "Don't show again"
+msgstr "Non mostrare più"
+
+msgid "Download"
+msgstr "Scarica"
+
+msgid "Download tar"
+msgstr "Scarica tar"
+
+msgid "Download tar.bz2"
+msgstr "Scarica tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Scarica tar.gz"
+
+msgid "Download zip"
+msgstr "Scarica zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Scarica"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Email Patches"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Differenze"
+
+msgid "DownloadSource|Download"
+msgstr "Scarica"
+
+msgid "Edit"
+msgstr "Modifica"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Cambia programmazione della pipeline %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ogni giorno (alle 4 del mattino)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Ogni primo giorno del mese (alle 4 del mattino)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ogni settimana (Di domenica alle 4 del mattino)"
+
+msgid "Failed to change the owner"
+msgstr "Impossibile cambiare owner"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Impossibile rimuovere la pipeline pianificata"
+
+msgid "Files"
+msgstr "Files"
+
+msgid "Filter by commit message"
+msgstr "Filtra per messaggio di commit"
+
+msgid "Find by path"
+msgstr "Trova in percorso"
+
+msgid "Find file"
+msgstr "Trova file"
+
+msgid "FirstPushedBy|First"
+msgstr "Primo"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "Push di"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fork"
+msgstr[1] "Forks"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Fork da"
+
+msgid "From issue creation until deploy to production"
+msgstr "Dalla creazione di un issue fino al rilascio in produzione"
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+"Dalla richiesta di merge fino effettua il merge fino al rilascio in "
+"produzione"
+
+msgid "Go to your fork"
+msgstr "Vai il tuo fork"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fork"
+
+msgid "Home"
+msgstr "Home"
+
+msgid "Housekeeping successfully started"
+msgstr "Housekeeping iniziato con successo"
+
+msgid "Import repository"
+msgstr "Importa repository"
+
+msgid "Interval Pattern"
+msgstr "Intervallo di Pattern"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Introduzione delle Analisi Cicliche"
+
+msgid "LFSStatus|Disabled"
+msgstr "Disabilitato"
+
+msgid "LFSStatus|Enabled"
+msgstr "Abilitato"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "L'ultimo %d giorno"
+msgstr[1] "Gli ultimi %d giorni"
+
+msgid "Last Pipeline"
+msgstr "Ultima Pipeline"
+
+msgid "Last Update"
+msgstr "Ultimo Aggiornamento"
+
+msgid "Last commit"
+msgstr "Ultimo Commit"
+
+msgid "Learn more in the"
+msgstr "Leggi di più su"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentazione sulla pianificazione delle pipelines"
+
+msgid "Leave group"
+msgstr "Abbandona il gruppo"
+
+msgid "Leave project"
+msgstr "Abbandona il progetto"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limita visualizzazione %d d'evento"
+msgstr[1] "Limita visualizzazione %d di eventi"
+
+msgid "Median"
+msgstr "Mediano"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "aggiungi una chiave SSH"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nuovo Issue"
+msgstr[1] "Nuovi Issues"
+
+msgid "New Pipeline Schedule"
+msgstr "Nuova pianificazione Pipeline"
+
+msgid "New branch"
+msgstr "Nuova Branch"
+
+msgid "New directory"
+msgstr "Nuova directory"
+
+msgid "New file"
+msgstr "Nuovo file"
+
+msgid "New issue"
+msgstr "Nuovo Issue"
+
+msgid "New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "New schedule"
+msgstr "Nuova pianficazione"
+
+msgid "New snippet"
+msgstr "Nuovo snippet"
+
+msgid "New tag"
+msgstr "Nuovo tag"
+
+msgid "No repository"
+msgstr "Nessuna Repository"
+
+msgid "No schedules"
+msgstr "Nessuna pianificazione"
+
+msgid "Not available"
+msgstr "Non disponibile"
+
+msgid "Not enough data"
+msgstr "Dati insufficienti "
+
+msgid "Notification events"
+msgstr "Notifica eventi"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Chiudi issue"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Chiudi richiesta di merge"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Pipeline fallita"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Completa la richiesta di merge"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nuovo issue"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "NotificationEvent|New note"
+msgstr "Nuova nota"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Riassegna issue"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Riassegna richiesta di Merge"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Riapri issue"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline Completata"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personalizzato"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Disabilitato"
+
+msgid "NotificationLevel|Global"
+msgstr "Globale"
+
+msgid "NotificationLevel|On mention"
+msgstr "Se menzionato"
+
+msgid "NotificationLevel|Participate"
+msgstr "Partecipa"
+
+msgid "NotificationLevel|Watch"
+msgstr "Osserva"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtra"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Aperto"
+
+msgid "Options"
+msgstr "Opzioni"
+
+msgid "Owner"
+msgstr "Owner"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
+msgid "Pipeline Health"
+msgstr "Stato della Pipeline"
+
+msgid "Pipeline Schedule"
+msgstr "Pianificazione Pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Pianificazione multipla Pipeline"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Attivata"
+
+msgid "PipelineSchedules|Active"
+msgstr "Attiva"
+
+msgid "PipelineSchedules|All"
+msgstr "Tutto"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inattiva"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Prossima esecuzione"
+
+msgid "PipelineSchedules|None"
+msgstr "Nessuna"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Fornisci una breve descrizione per questa pipeline"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Prendi possesso"
+
+msgid "PipelineSchedules|Target"
+msgstr "Target"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personalizzato"
+
+msgid "Pipeline|with stage"
+msgstr "con stadio"
+
+msgid "Pipeline|with stages"
+msgstr "con più stadi"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Il Progetto '%{project_name}' in coda di eliminazione."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Il Progetto '%{project_name}' è stato creato con successo."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Il Progetto '%{project_name}' verrà eliminato"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente"
+
+msgid "Project export could not be deleted."
+msgstr "L'esportazione del progetto non può essere eliminata."
+
+msgid "Project export has been deleted."
+msgstr "L'esportazione del progetto è stata eliminata."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Il link d'esportazione del progetto è scaduto. Genera una nuova esportazione "
+"dalle impostazioni del tuo progetto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Esportazione del progetto iniziata. Un link di download sarà inviato via "
+"email."
+
+msgid "Project home"
+msgstr "Home di progetto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Disabilitato"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Chiunque con accesso"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Solo i membri del team"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nome"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Mai"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Stadio"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Grafico"
+
+msgid "Read more"
+msgstr "Continua..."
+
+msgid "Readme"
+msgstr "Leggimi"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Tags"
+
+msgid "Related Commits"
+msgstr "Commit correlati"
+
+msgid "Related Deployed Jobs"
+msgstr "Attività di Rilascio Correlate"
+
+msgid "Related Issues"
+msgstr "Issues Correlati"
+
+msgid "Related Jobs"
+msgstr "Attività Correlate"
+
+msgid "Related Merge Requests"
+msgstr "Richieste di Merge Correlate"
+
+msgid "Related Merged Requests"
+msgstr "Richieste di Merge Completate Correlate"
+
+msgid "Remind later"
+msgstr "Ricordamelo più tardi"
+
+msgid "Remove project"
+msgstr "Rimuovi progetto"
+
+msgid "Request Access"
+msgstr "Richiedi accesso"
+
+msgid "Revert this commit"
+msgstr "Ripristina questo commit"
+
+msgid "Revert this merge request"
+msgstr "Ripristina questa richiesta di merge"
+
+msgid "Save pipeline schedule"
+msgstr "Salva pianificazione pipeline"
+
+msgid "Schedule a new pipeline"
+msgstr "Pianifica una nuova Pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Pianificazione pipelines"
+
+msgid "Search branches and tags"
+msgstr "Ricerca branches e tags"
+
+msgid "Select Archive Format"
+msgstr "Seleziona formato d'archivio"
+
+msgid "Select a timezone"
+msgstr "Seleziona una timezone"
+
+msgid "Select target branch"
+msgstr "Seleziona una branch di destinazione"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Imposta una password sul tuo account per eseguire pull o push tramite "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "Configura CI"
+
+msgid "Set up Koding"
+msgstr "Configura Koding"
+
+msgid "Set up auto deploy"
+msgstr "Configura il rilascio automatico"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "imposta una password"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Visualizza %d evento"
+msgstr[1] "Visualizza %d eventi"
+
+msgid "Source code"
+msgstr "Codice Sorgente"
+
+msgid "StarProject|Star"
+msgstr "Star"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "inizia una %{new_merge_request} con queste modifiche"
+
+msgid "Switch branch/tag"
+msgstr "Cambia branch/tag"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Tag"
+msgstr[1] "Tags"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Target Branch"
+msgstr "Branch di destinazione"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"Lo stadio di programmazione mostra il tempo trascorso dal primo commit alla "
+"creazione di una richiesta di merge (MR). I dati saranno aggiunti una volta "
+"che avrai creato la prima richiesta di merge."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio."
+
+msgid "The fork relationship has been removed."
+msgstr "La relazione del fork è stata rimossa"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue "
+"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea "
+"un issue per vedere questo stadio."
+
+msgid "The phase of the development lifecycle."
+msgstr "Il ciclo vitale della fase di sviluppo."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per "
+"specifici tag o branch ed ereditano restrizioni di progetto basate "
+"sull'utente ad esse associato."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al "
+"suo step precedente. Questo periodo sarà disponibile automaticamente nel "
+"momento in cui farai il primo commit."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"Lo stadio di produzione mostra il tempo totale che trascorre tra la "
+"creazione di un issue il suo rilascio (inteso come codice) in produzione. "
+"Questo dato sarà disponibile automaticamente nel momento in cui avrai "
+"completato l'intero processo ideale del ciclo di produzione"
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Qualunque utente autenticato può accedere a questo progetto."
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+"Chiunque può accedere a questo progetto (senza alcuna autenticazione)."
+
+msgid "The repository for this project does not exist."
+msgstr "La repository di questo progetto non esiste."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo "
+"svolgimento effettivo. Questo dato sarà disponibile appena avrai completato "
+"una MR (Merger Request)"
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta "
+"di Merge) completata al suo rilascio in ambiente di produzione. Questa "
+"informazione sarà disponibile dal tuo primo rilascio in produzione"
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"Lo stadio di test mostra il tempo che ogni Pipeline impiega per essere "
+"eseguita in ogni Richiesta di Merge correlata. L'informazione sarà "
+"disponibile automaticamente quando la tua prima Pipeline avrà finito d'esser "
+"eseguita."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+"Il tempo aggregato relativo eventi/data entry raccolto in quello stadio."
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il "
+"mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Questo significa che non è possibile effettuare push di codice fino a che "
+"non crei una repository vuota o ne importi una esistente"
+
+msgid "Time before an issue gets scheduled"
+msgstr "Il tempo che impiega un issue per esser pianificato"
+
+msgid "Time before an issue starts implementation"
+msgstr "Il tempo che impiega un issue per esser implementato"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close"
+
+msgid "Time until first merge request"
+msgstr "Il tempo fino alla prima richiesta di merge"
+
+msgid "Timeago|%s days ago"
+msgstr "%s giorni fa"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s giorni rimanenti"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s ore rimanenti"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s minuti rimanenti"
+
+msgid "Timeago|%s months ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s mesi rimanenti"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s secondi rimanenti"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s settimane fa"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s settimane rimanenti"
+
+msgid "Timeago|%s years ago"
+msgstr "%s anni fa"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s anni rimanenti"
+
+msgid "Timeago|1 day remaining"
+msgstr "1 giorno rimanente"
+
+msgid "Timeago|1 hour remaining"
+msgstr "1 ora rimanente"
+
+msgid "Timeago|1 minute remaining"
+msgstr "1 minuto rimanente"
+
+msgid "Timeago|1 month remaining"
+msgstr "1 mese rimanente"
+
+msgid "Timeago|1 week remaining"
+msgstr "1 settimana rimanente"
+
+msgid "Timeago|1 year remaining"
+msgstr "1 anno rimanente"
+
+msgid "Timeago|Past due"
+msgstr "Entro"
+
+msgid "Timeago|a day ago"
+msgstr "un giorno fa"
+
+msgid "Timeago|a month ago"
+msgstr "un mese fa"
+
+msgid "Timeago|a week ago"
+msgstr "una settimana fa"
+
+msgid "Timeago|a while"
+msgstr "poco fa"
+
+msgid "Timeago|a year ago"
+msgstr "un anno fa"
+
+msgid "Timeago|about %s hours ago"
+msgstr "circa %s ore fa"
+
+msgid "Timeago|about a minute ago"
+msgstr "circa un minuto fa"
+
+msgid "Timeago|about an hour ago"
+msgstr "circa un ora fa"
+
+msgid "Timeago|in %s days"
+msgstr "in %s giorni"
+
+msgid "Timeago|in %s hours"
+msgstr "in %s ore"
+
+msgid "Timeago|in %s minutes"
+msgstr "in %s minuti"
+
+msgid "Timeago|in %s months"
+msgstr "in %s mesi"
+
+msgid "Timeago|in %s seconds"
+msgstr "in %s secondi"
+
+msgid "Timeago|in %s weeks"
+msgstr "in %s settimane"
+
+msgid "Timeago|in %s years"
+msgstr "in %s anni"
+
+msgid "Timeago|in 1 day"
+msgstr "in 1 giorno"
+
+msgid "Timeago|in 1 hour"
+msgstr "in 1 ora"
+
+msgid "Timeago|in 1 minute"
+msgstr "in 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "in 1 mese"
+
+msgid "Timeago|in 1 week"
+msgstr "in 1 settimana"
+
+msgid "Timeago|in 1 year"
+msgstr "in 1 anno"
+
+msgid "Timeago|less than a minute ago"
+msgstr "meno di un minuto fa"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "hr"
+msgstr[1] "hr"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "mins"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Tempo Totale"
+
+msgid "Total test time for all commits/merges"
+msgstr "Tempo totale di test per tutti i commits/merges"
+
+msgid "Unstar"
+msgstr "Unstar"
+
+msgid "Upload New File"
+msgstr "Carica un nuovo file"
+
+msgid "Upload file"
+msgstr "Carica file"
+
+msgid "UploadLink|click to upload"
+msgstr "clicca per caricare"
+
+msgid "Use your global notification setting"
+msgstr "Usa le tue impostazioni globali "
+
+msgid "View open merge request"
+msgstr "Mostra la richieste di merge aperte"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interno"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privato"
+
+msgid "VisibilityLevel|Public"
+msgstr "Pubblico"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
+
+msgid "Withdraw Access Request"
+msgstr "Ritira richiesta d'accesso"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere %{project_name_with_namespace}.\n"
+"I progetti rimossi NON POSSONO essere ripristinati\n"
+"Sei assolutamente sicuro?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere la relazione con il progetto sorgente "
+"%{forked_from_project}. Sei ASSOLUTAMENTE sicuro?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei "
+"ASSOLUTAMENTE sicuro?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Puoi aggiungere files solo quando sei in una branch"
+
+msgid "You have reached your project limit"
+msgstr "Hai raggiunto il tuo limite di progetto"
+
+msgid "You must sign in to star a project"
+msgstr "Devi accedere per porre una star al progetto"
+
+msgid "You need permission."
+msgstr "Necessiti del permesso."
+
+msgid "You will not get any notifications via email"
+msgstr "Non riceverai alcuna notifica via email"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Riceverai notifiche solo per gli eventi che hai scelto"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Riceverai notifiche solo per i threads a cui hai partecipato"
+
+msgid "You will receive notifications for any activity"
+msgstr "Riceverai notifiche per ogni attività"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Riceverai notifiche solo per i commenti ai quale sei stato menzionato"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Non sarai in grado di eseguire pull o push di codice tramite %{protocol} "
+"fino a che %{set_password_link} nel tuo account."
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Non sarai in grado di effettuare push o pull tramite SSH fino a che "
+"%{add_ssh_key_link} al tuo profilo"
+
+msgid "Your name"
+msgstr "Il tuo nome"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "giorno"
+msgstr[1] "giorni"
+
+msgid "new merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "notification emails"
+msgstr "Notifiche via email"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "parent"
+msgstr[1] "parents"
+
diff --git a/locale/it/gitlab.po.time_stamp b/locale/it/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/it/gitlab.po.time_stamp
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 8ba95093b82..2f21aae2899 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -4,17 +4,26 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-19 09:57-0400\n"
+"PO-Revision-Date: 2017-06-27 03:18-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "为提高页面加载速度及性能,已省略了 %d 次提交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 次提交"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "由 %{commit_author_link} 提交于 %{commit_timeago}"
@@ -63,9 +72,24 @@ msgstr ""
"已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择合适的 GitLab CI Yaml "
"模板并提交更改。%{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "搜索分支"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切换分支"
+
msgid "Branches"
msgstr "分支"
+msgid "Browse Directory"
+msgstr "浏览目录"
+
+msgid "Browse File"
+msgstr "浏览文件"
+
+msgid "Browse Files"
+msgstr "浏览文件"
+
msgid "Browse files"
msgstr "浏览文件"
@@ -172,6 +196,9 @@ msgstr "添加 %{file_name}"
msgid "Commits"
msgstr "提交"
+msgid "Commits feed"
+msgstr "提交动态"
+
msgid "Commits|History"
msgstr "历史"
@@ -329,6 +356,9 @@ msgstr "无法删除流水线计划"
msgid "Files"
msgstr "文件"
+msgid "Filter by commit message"
+msgstr "按提交消息过滤"
+
msgid "Find by path"
msgstr "按路径查找"
@@ -692,7 +722,7 @@ msgstr "选择时区"
msgid "Select target branch"
msgstr "选择目标分支"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "为账号创建一个用于推送或拉取的 %{protocol} 密码。"
msgid "Set up CI"
@@ -974,9 +1004,15 @@ msgstr "上传新文件"
msgid "Upload file"
msgstr "上传文件"
+msgid "UploadLink|click to upload"
+msgstr "点击上传"
+
msgid "Use your global notification setting"
msgstr "使用全局通知设置"
+msgid "View open merge request"
+msgstr "查看待处理的合并请求"
+
msgid "VisibilityLevel|Internal"
msgstr "内部"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 4d545d27185..afdbd01b7d7 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -9,13 +9,22 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-19 09:57-0400\n"
+"PO-Revision-Date: 2017-06-23 01:23-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-HK\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "為提高頁面加載速度及性能,已省略了 %d 次提交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] " %d 次提交"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}"
@@ -64,9 +73,24 @@ msgstr ""
"分支 <strong>%{branch_name}</strong> 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml "
"模板併提交更改。%{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "搜索分支"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切換分支"
+
msgid "Branches"
msgstr "分支"
+msgid "Browse Directory"
+msgstr "瀏覽目錄"
+
+msgid "Browse File"
+msgstr "瀏覽文件"
+
+msgid "Browse Files"
+msgstr "瀏覽文件"
+
msgid "Browse files"
msgstr "瀏覽文件"
@@ -173,6 +197,9 @@ msgstr "添加 %{file_name}"
msgid "Commits"
msgstr "提交"
+msgid "Commits feed"
+msgstr "提交動態"
+
msgid "Commits|History"
msgstr "歷史"
@@ -330,6 +357,9 @@ msgstr "無法刪除流水線計劃"
msgid "Files"
msgstr "文件"
+msgid "Filter by commit message"
+msgstr "按提交消息過濾"
+
msgid "Find by path"
msgstr "按路徑查找"
@@ -693,7 +723,7 @@ msgstr "選擇時區"
msgid "Select target branch"
msgstr "選擇目標分支"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"
msgid "Set up CI"
@@ -975,9 +1005,15 @@ msgstr "上傳新文件"
msgid "Upload file"
msgstr "上傳文件"
+msgid "UploadLink|click to upload"
+msgstr "點擊上傳"
+
msgid "Use your global notification setting"
msgstr "使用全局通知設置"
+msgid "View open merge request"
+msgstr "查看開啟的合並請求"
+
msgid "VisibilityLevel|Internal"
msgstr "內部"
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 5130572d7ed..fa0b3b339fa 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -1,128 +1,490 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Lin Jen-Shin <anonymous@domain.com>, 2017.
+# Hazel Yang <anonymous@domain.com>, 2017.
+# TzeKei Lee <anonymous@domain.com>, 2017.
+# Jerry Ho <a29988122@gmail.com>, 2017.
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-05-04 19:24-0500\n"
-"Last-Translator: HuangTao <htve@outlook.com>, 2017\n"
-"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/751"
-"77/zh_TW/)\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: zh_TW\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"PO-Revision-Date: 2017-06-28 11:13-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: zh-TW\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "因效能考量,不顯示 %d 個更動 (commit)。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 個更動 (commit)"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} 在 %{commit_timeago} 送交"
+
+msgid "About auto deploy"
+msgstr "關於自動部署"
+
+msgid "Active"
+msgstr "啟用"
+
+msgid "Activity"
+msgstr "活動"
+
+msgid "Add Changelog"
+msgstr "新增更新日誌"
+
+msgid "Add Contribution guide"
+msgstr "新增協作指南"
+
+msgid "Add License"
+msgstr "新增授權條款"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "請先新增 SSH 金鑰到您的個人帳號,才能使用 SSH 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Add new directory"
+msgstr "新增目錄"
+
+msgid "Archived project! Repository is read-only"
+msgstr "此專案已封存!檔案庫 (repository) 為唯讀狀態"
msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "確定要刪除此流水線 (pipeline) 排程嗎?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "拖放檔案到此處或者 %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "分支 (branch) "
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
msgstr ""
+"已建立分支 (branch) <strong>%{branch_name}</strong> 。如要設定自動部署, 請選擇合適的 GitLab CI "
+"Yaml 模板,然後記得要送交 (commit) 您的編輯內容。%{link_to_autodeploy_doc}\n"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "搜尋分支 (branches)"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切換分支 (branch)"
+
+msgid "Branches"
+msgstr "分支 (branch) "
+
+msgid "Browse Directory"
+msgstr "瀏覽目錄"
+
+msgid "Browse File"
+msgstr "瀏覽檔案"
+
+msgid "Browse Files"
+msgstr "瀏覽檔案"
+
+msgid "Browse files"
+msgstr "瀏覽檔案"
msgid "ByAuthor|by"
-msgstr "作者:"
+msgstr "作者:"
+
+msgid "CI configuration"
+msgstr "CI 組態"
msgid "Cancel"
-msgstr ""
+msgstr "取消"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "挑選到分支 (branch) "
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "還原分支 (branch) "
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "挑選"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "還原"
+
+msgid "Changelog"
+msgstr "更新日誌"
+
+msgid "Charts"
+msgstr "統計圖"
+
+msgid "Cherry-pick this commit"
+msgstr "挑選此更動記錄 (commit) "
+
+msgid "Cherry-pick this merge request"
+msgstr "挑選此合併請求 (merge request) "
+
+msgid "CiStatusLabel|canceled"
+msgstr "已取消"
+
+msgid "CiStatusLabel|created"
+msgstr "已建立"
+
+msgid "CiStatusLabel|failed"
+msgstr "失敗"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手動操作"
+
+msgid "CiStatusLabel|passed"
+msgstr "已通過"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "通過,但有警告訊息"
+
+msgid "CiStatusLabel|pending"
+msgstr "等待中"
+
+msgid "CiStatusLabel|skipped"
+msgstr "已跳過"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "等待手動操作"
+
+msgid "CiStatusText|blocked"
+msgstr "已阻擋"
+
+msgid "CiStatusText|canceled"
+msgstr "已取消"
+
+msgid "CiStatusText|created"
+msgstr "已建立"
+
+msgid "CiStatusText|failed"
+msgstr "失敗"
+
+msgid "CiStatusText|manual"
+msgstr "手動操作"
+
+msgid "CiStatusText|passed"
+msgstr "已通過"
+
+msgid "CiStatusText|pending"
+msgstr "等待中"
+
+msgid "CiStatusText|skipped"
+msgstr "已跳過"
+
+msgid "CiStatus|running"
+msgstr "執行中"
msgid "Commit"
msgid_plural "Commits"
-msgstr[0] "送交"
+msgstr[0] "更動記錄 (commit) "
+
+msgid "Commit message"
+msgstr "更動說明 (commit) "
+
+msgid "CommitBoxTitle|Commit"
+msgstr "送交"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "建立 %{file_name}"
+
+msgid "Commits"
+msgstr "更動記錄 (commit) "
+
+msgid "Commits feed"
+msgstr "更動摘要 (commit feed)"
+
+msgid "Commits|History"
+msgstr "過去更動 (commit) "
+
+msgid "Committed by"
+msgstr "送交者為 "
+
+msgid "Compare"
+msgstr "比較"
+
+msgid "Contribution guide"
+msgstr "協作指南"
+
+msgid "Contributors"
+msgstr "協作者"
+
+msgid "Copy URL to clipboard"
+msgstr "複製網址到剪貼簿"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿"
+
+msgid "Create New Directory"
+msgstr "建立新目錄"
+
+msgid "Create directory"
+msgstr "建立目錄"
+
+msgid "Create empty bare repository"
+msgstr "建立一個新的 bare repository"
+
+msgid "Create merge request"
+msgstr "發出合併請求 (merge request) "
+
+msgid "Create new..."
+msgstr "建立..."
+
+msgid "CreateNewFork|Fork"
+msgstr "分支 (fork) "
+
+msgid "CreateTag|Tag"
+msgstr "建立標籤"
msgid "Cron Timezone"
+msgstr "Cron 時區"
+
+msgid "Cron syntax"
+msgstr "Cron 語法"
+
+msgid "Custom notification events"
+msgstr "自訂事件通知"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
msgstr ""
+"自訂通知層級相當於參與度設定。使用自訂通知層級,您可以只收到特定的事件通知。請參照 %{notification_link} 以獲得更多訊息。"
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "週期分析概述了你的專案從想法到產品實現,各階段所需的時間。"
+msgid "Cycle Analytics"
+msgstr "週期分析"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr "週期分析讓您可以有效的釐清專案從發想到產品推出所花的時間長短。"
msgid "CycleAnalyticsStage|Code"
msgstr "程式開發"
msgid "CycleAnalyticsStage|Issue"
-msgstr "議題"
+msgstr "議題 (issue) "
msgid "CycleAnalyticsStage|Plan"
msgstr "計劃"
msgid "CycleAnalyticsStage|Production"
-msgstr "上線"
+msgstr "營運"
msgid "CycleAnalyticsStage|Review"
msgstr "複閱"
msgid "CycleAnalyticsStage|Staging"
-msgstr "預備"
+msgstr "試營運"
msgid "CycleAnalyticsStage|Test"
msgstr "測試"
+msgid "Define a custom pattern with cron syntax"
+msgstr "使用 Cron 語法自訂排程"
+
msgid "Delete"
-msgstr ""
+msgstr "刪除"
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "部署"
msgid "Description"
-msgstr ""
+msgstr "描述"
+
+msgid "Directory name"
+msgstr "目錄名稱"
+
+msgid "Don't show again"
+msgstr "不再顯示"
+
+msgid "Download"
+msgstr "下載"
+
+msgid "Download tar"
+msgstr "下載 tar"
+
+msgid "Download tar.bz2"
+msgstr "下載 tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "下載 tar.gz"
+
+msgid "Download zip"
+msgstr "下載 zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "下載"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "電子郵件修補檔案 (patch)"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "差異檔 (diff)"
+
+msgid "DownloadSource|Download"
+msgstr "下載原始碼"
msgid "Edit"
-msgstr ""
+msgstr "編輯"
msgid "Edit Pipeline Schedule %{id}"
-msgstr ""
+msgstr "編輯 %{id} 流水線 (pipeline) 排程"
+
+msgid "Every day (at 4:00am)"
+msgstr "每日執行(淩晨四點)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "每月執行(每月一日淩晨四點)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "每週執行(週日淩晨 四點)"
msgid "Failed to change the owner"
-msgstr ""
+msgstr "無法變更所有權"
msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "無法刪除流水線 (pipeline) 排程"
-msgid "Filter"
-msgstr ""
+msgid "Files"
+msgstr "檔案"
+
+msgid "Filter by commit message"
+msgstr "以更動說明篩選"
+
+msgid "Find by path"
+msgstr "以路徑搜尋"
+
+msgid "Find file"
+msgstr "搜尋檔案"
msgid "FirstPushedBy|First"
-msgstr "首次推送"
+msgstr "首次推送 (push) "
msgid "FirstPushedBy|pushed by"
-msgstr "推送者:"
+msgstr "推送者 (push) :"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "分支 (fork) "
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "分支 (fork) 自"
msgid "From issue creation until deploy to production"
-msgstr "從議題建立至線上部署"
+msgstr "從議題 (issue) 建立直到部署至營運環境"
msgid "From merge request merge until deploy to production"
-msgstr "從請求被合併後至線上部署"
+msgstr "從請求被合併後 (merge request merged) 直到部署至營運環境"
+
+msgid "Go to your fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "GoToYourFork|Fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "Home"
+msgstr "首頁"
+
+msgid "Housekeeping successfully started"
+msgstr "已開始維護"
+
+msgid "Import repository"
+msgstr "匯入檔案庫 (repository)"
msgid "Interval Pattern"
-msgstr ""
+msgstr "循環週期"
msgid "Introducing Cycle Analytics"
msgstr "週期分析簡介"
+msgid "LFSStatus|Disabled"
+msgstr "停用"
+
+msgid "LFSStatus|Enabled"
+msgstr "啟用"
+
msgid "Last %d day"
msgid_plural "Last %d days"
-msgstr[0] "最後 %d 天"
+msgstr[0] "最近 %d 天"
msgid "Last Pipeline"
-msgstr ""
+msgstr "最新流水線 (pipeline) "
+
+msgid "Last Update"
+msgstr "最後更新"
+
+msgid "Last commit"
+msgstr "最後更動記錄 (commit) "
+
+msgid "Learn more in the"
+msgstr "了解更多"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "流水線 (pipeline) 排程說明文件"
+
+msgid "Leave group"
+msgstr "退出群組"
+
+msgid "Leave project"
+msgstr "退出專案"
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
-msgstr[0] "最多顯示 %d 個事件"
+msgstr[0] "限制最多顯示 %d 個事件"
msgid "Median"
msgstr "中位數"
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "新增 SSH 金鑰"
+
msgid "New Issue"
msgid_plural "New Issues"
-msgstr[0] "新議題"
+msgstr[0] "建立議題 (issue) "
msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "New branch"
+msgstr "新分支 (branch) "
+
+msgid "New directory"
+msgstr "新增目錄"
+
+msgid "New file"
+msgstr "新增檔案"
+
+msgid "New issue"
+msgstr "新增議題 (issue) "
+
+msgid "New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "New schedule"
+msgstr "新增排程"
+
+msgid "New snippet"
+msgstr "新文字片段"
+
+msgid "New tag"
+msgstr "新增標籤"
+
+msgid "No repository"
+msgstr "找不到檔案庫 (repository)"
msgid "No schedules"
-msgstr ""
+msgstr "沒有排程"
msgid "Not available"
msgstr "無法使用"
@@ -130,135 +492,502 @@ msgstr "無法使用"
msgid "Not enough data"
msgstr "資料不足"
+msgid "Notification events"
+msgstr "事件通知"
+
+msgid "NotificationEvent|Close issue"
+msgstr "關閉議題 (issue) "
+
+msgid "NotificationEvent|Close merge request"
+msgstr "關閉合併請求 (merge request) "
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "流水線 (pipeline) 失敗"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "合併請求 (merge request) 被合併"
+
+msgid "NotificationEvent|New issue"
+msgstr "新增議題 (issue) "
+
+msgid "NotificationEvent|New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "NotificationEvent|New note"
+msgstr "新增評論"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "重新指派議題 (issue) "
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "重新指派合併請求 (merge request) "
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "重啟議題 (issue)"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "流水線 (pipeline) 成功完成"
+
+msgid "NotificationLevel|Custom"
+msgstr "自訂"
+
+msgid "NotificationLevel|Disabled"
+msgstr "停用"
+
+msgid "NotificationLevel|Global"
+msgstr "全域"
+
+msgid "NotificationLevel|On mention"
+msgstr "提及"
+
+msgid "NotificationLevel|Participate"
+msgstr "參與"
+
+msgid "NotificationLevel|Watch"
+msgstr "關注"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "篩選"
+
msgid "OpenedNDaysAgo|Opened"
msgstr "開始於"
+msgid "Options"
+msgstr "選項"
+
msgid "Owner"
-msgstr ""
+msgstr "所有權"
+
+msgid "Pipeline"
+msgstr "流水線 (pipeline) "
msgid "Pipeline Health"
-msgstr "流水線健康指標"
+msgstr "流水線 (pipeline) 健康指數"
msgid "Pipeline Schedule"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
msgid "Pipeline Schedules"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "是否啟用"
msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "已啟用"
msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "所有"
msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "未啟用"
msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "下次執行時間"
msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "無"
msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "請簡單說明此流水線 (pipeline) "
msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "取得所有權"
msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "目標"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "自訂"
+
+msgid "Pipeline|with stage"
+msgstr "於階段"
+
+msgid "Pipeline|with stages"
+msgstr "於階段"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "專案 '%{project_name}' 已加入刪除佇列。"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "專案 '%{project_name}' 建立完成。"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "專案 '%{project_name}' 更新完成。"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "專案 '%{project_name}' 將被刪除。"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "專案權限必須一一指派給每個使用者。"
+
+msgid "Project export could not be deleted."
+msgstr "匯出的專案無法被刪除。"
+
+msgid "Project export has been deleted."
+msgstr "匯出的專案已被刪除。"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "專案的匯出連結已失效。請到專案設定中產生新的連結。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "專案導出已開始。完成後下載連結會送到您的信箱。"
+
+msgid "Project home"
+msgstr "專案首頁"
+
+msgid "ProjectFeature|Disabled"
+msgstr "停用"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "任何人都可存取"
+
+msgid "ProjectFeature|Only team members"
+msgstr "只有團隊成員可以存取"
+
+msgid "ProjectFileTree|Name"
+msgstr "名稱"
+
+msgid "ProjectLastActivity|Never"
+msgstr "從未"
msgid "ProjectLifecycle|Stage"
-msgstr "專案生命週期"
+msgstr "階段"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "分支圖"
msgid "Read more"
-msgstr "了解更多"
+msgstr "瞭解更多"
+
+msgid "Readme"
+msgstr "說明檔"
+
+msgid "RefSwitcher|Branches"
+msgstr "分支 (branch) "
+
+msgid "RefSwitcher|Tags"
+msgstr "標籤"
msgid "Related Commits"
-msgstr "相關的送交"
+msgstr "相關的更動記錄 (commit) "
msgid "Related Deployed Jobs"
msgstr "相關的部署作業"
msgid "Related Issues"
-msgstr "相關的議題"
+msgstr "相關的議題 (issue) "
msgid "Related Jobs"
msgstr "相關的作業"
msgid "Related Merge Requests"
-msgstr "相關的合併請求"
+msgstr "相關的合併請求 (merge request) "
msgid "Related Merged Requests"
msgstr "相關已合併的請求"
+msgid "Remind later"
+msgstr "稍後提醒"
+
+msgid "Remove project"
+msgstr "刪除專案"
+
+msgid "Request Access"
+msgstr "申請權限"
+
+msgid "Revert this commit"
+msgstr "還原此更動記錄 (commit)"
+
+msgid "Revert this merge request"
+msgstr "還原此合併請求 (merge request) "
+
msgid "Save pipeline schedule"
-msgstr ""
+msgstr "保存流水線 (pipeline) 排程"
msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "Scheduling Pipelines"
+msgstr "流水線 (pipeline) 計劃"
+
+msgid "Search branches and tags"
+msgstr "搜尋分支 (branch) 和標籤"
+
+msgid "Select Archive Format"
+msgstr "選擇下載格式"
msgid "Select a timezone"
-msgstr ""
+msgstr "選擇時區"
msgid "Select target branch"
-msgstr ""
+msgstr "選擇目標分支 (branch) "
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "請先設定密碼,才能使用 %{protocol} 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Set up CI"
+msgstr "設定 CI"
+
+msgid "Set up Koding"
+msgstr "設定 Koding"
+
+msgid "Set up auto deploy"
+msgstr "設定自動部署"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "設定密碼"
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "顯示 %d 個事件"
+msgid "Source code"
+msgstr "原始碼"
+
+msgid "StarProject|Star"
+msgstr "收藏"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "以這些改動建立一個新的 %{new_merge_request} "
+
+msgid "Switch branch/tag"
+msgstr "切換分支 (branch) 或標籤"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "標籤"
+
+msgid "Tags"
+msgstr "標籤"
+
msgid "Target Branch"
-msgstr ""
+msgstr "目標分支 (branch) "
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。"
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"程式開發階段顯示從第一次更動記錄 (commit) 到建立合併請求 (merge request) 的時間。建立第一個合併請求後,資料將自動填入。"
msgid "The collection of events added to the data gathered for that stage."
-msgstr "與該階段相關的事件。"
+msgstr "該階段中的相關事件集合。"
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。"
+msgid "The fork relationship has been removed."
+msgstr "分支與主幹間的關聯 (fork relationship) 已被刪除。"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"議題 (issue) 階段顯示從議題建立到設定里程碑所花的時間,或是議題被分類到議題看板 (issue board) "
+"中所花的時間。建立第一個議題後,資料將自動填入。"
msgid "The phase of the development lifecycle."
-msgstr "專案開發生命週期的各個階段。"
+msgstr "專案開發週期的各個階段。"
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。\n"
+"流水線排程的存取權限與專案本身相同。"
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "計劃階段所顯示的是議題被排程後至第一個送交被推送的時間。一旦完成(或執行)首次的推送,資料將自動填入。"
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "計劃階段顯示從更動記錄 (commit) 被排程至第一個推送的時間。第一次推送之後,資料將自動填入。"
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr "營運階段顯示從建立議題 (issue) 到部署程式上線所花的時間。完成從發想到上線的完整開發週期後,資料將自動填入。"
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "上線階段顯示從建立一個議題到部署程式至線上的總時間。當完成從想法到產品實現的循環後,資料將自動填入。"
+msgid "The project can be accessed by any logged in user."
+msgstr "本專案可讓任何已登入的使用者存取"
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "複閱階段顯示從合併請求建立後至被合併的時間。當建立第一個合併請求後,資料將自動填入。"
+msgid "The project can be accessed without any authentication."
+msgstr "本專案可讓任何人存取"
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "預備階段顯示從合併請求被合併後至部署上線的時間。當第一次部署上線後,資料將自動填入。"
+msgid "The repository for this project does not exist."
+msgstr "本專案沒有檔案庫 (repository) "
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。"
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"複閱階段顯示從合併請求 (merge request) 建立後至被合併的時間。當建立第一個合併請求 (merge request) 後,資料將自動填入。"
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr "試營運段顯示從合併請求 (merge request) 被合併後至部署營運的時間。當第一次部署營運後,資料將自動填入"
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"測試階段顯示相關合併請求 (merge request) 的流水線 (pipeline) 所花的時間。當第一個流水線 (pipeline) "
+"執行完畢後,資料將自動填入。"
msgid "The time taken by each data entry gathered by that stage."
-msgstr "每筆該階段相關資料所花的時間。"
+msgstr "該階段中每一個資料項目所花的時間。"
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個現存的檔案庫之前,您將無法上傳更新 (push) 。"
+
msgid "Time before an issue gets scheduled"
-msgstr "議題等待排程的時間"
+msgstr "議題 (issue) 被列入日程表的時間"
msgid "Time before an issue starts implementation"
-msgstr "議題等待開始實作的時間"
+msgstr "議題 (issue) 等待開始實作的時間"
msgid "Time between merge request creation and merge/close"
-msgstr "合併請求被合併或是關閉的時間"
+msgstr "合併請求 (merge request) 從建立到被合併或是關閉的時間"
msgid "Time until first merge request"
-msgstr "第一個合併請求被建立前的時間"
+msgstr "第一個合併請求 (merge request) 被建立前的時間"
+
+msgid "Timeago|%s days ago"
+msgstr " %s 天前"
+
+msgid "Timeago|%s days remaining"
+msgstr "剩下 %s 天"
+
+msgid "Timeago|%s hours remaining"
+msgstr "剩下 %s 小時"
+
+msgid "Timeago|%s minutes ago"
+msgstr " %s 分鐘前"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "剩下 %s 分鐘"
+
+msgid "Timeago|%s months ago"
+msgstr " %s 個月前"
+
+msgid "Timeago|%s months remaining"
+msgstr "剩下 %s 月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "剩下 %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr " %s 週前"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "剩下 %s 週"
+
+msgid "Timeago|%s years ago"
+msgstr " %s 年前"
+
+msgid "Timeago|%s years remaining"
+msgstr "剩下 %s 年"
+
+msgid "Timeago|1 day remaining"
+msgstr "剩下 1 天"
+
+msgid "Timeago|1 hour remaining"
+msgstr "剩下 1 小時"
+
+msgid "Timeago|1 minute remaining"
+msgstr "剩下 1 分鐘"
+
+msgid "Timeago|1 month remaining"
+msgstr "剩下 1 個月"
+
+msgid "Timeago|1 week remaining"
+msgstr "剩下 1 週"
+
+msgid "Timeago|1 year remaining"
+msgstr "剩下 1 年"
+
+msgid "Timeago|Past due"
+msgstr "逾期"
+
+msgid "Timeago|a day ago"
+msgstr " 1 天前"
+
+msgid "Timeago|a month ago"
+msgstr " 1 個月前"
+
+msgid "Timeago|a week ago"
+msgstr " 1 週前"
+
+msgid "Timeago|a while"
+msgstr "剛剛"
+
+msgid "Timeago|a year ago"
+msgstr " 1 年前"
+
+msgid "Timeago|about %s hours ago"
+msgstr "約 %s 小時前"
+
+msgid "Timeago|about a minute ago"
+msgstr "約 1 分鐘前"
+
+msgid "Timeago|about an hour ago"
+msgstr "約 1 小時前"
+
+msgid "Timeago|in %s days"
+msgstr " %s 天後"
+
+msgid "Timeago|in %s hours"
+msgstr " %s 小時後"
+
+msgid "Timeago|in %s minutes"
+msgstr " %s 分鐘後"
+
+msgid "Timeago|in %s months"
+msgstr " %s 個月後"
+
+msgid "Timeago|in %s seconds"
+msgstr " %s 秒後"
+
+msgid "Timeago|in %s weeks"
+msgstr " %s 週後"
+
+msgid "Timeago|in %s years"
+msgstr " %s 年後"
+
+msgid "Timeago|in 1 day"
+msgstr " 1 天後"
+
+msgid "Timeago|in 1 hour"
+msgstr " 1 小時後"
+
+msgid "Timeago|in 1 minute"
+msgstr " 1 分鐘後"
+
+msgid "Timeago|in 1 month"
+msgstr " 1 個月後"
+
+msgid "Timeago|in 1 week"
+msgstr " 1 週後"
+
+msgid "Timeago|in 1 year"
+msgstr " 1 年後"
+
+msgid "Timeago|less than a minute ago"
+msgstr "不到 1 分鐘前"
msgid "Time|hr"
msgid_plural "Time|hrs"
@@ -275,7 +1004,34 @@ msgid "Total Time"
msgstr "總時間"
msgid "Total test time for all commits/merges"
-msgstr "所有送交和合併的總測試時間"
+msgstr "合併 (merge) 與更動記錄 (commit) 的總測試時間"
+
+msgid "Unstar"
+msgstr "取消收藏"
+
+msgid "Upload New File"
+msgstr "上傳新檔案"
+
+msgid "Upload file"
+msgstr "上傳檔案"
+
+msgid "UploadLink|click to upload"
+msgstr "點擊上傳"
+
+msgid "Use your global notification setting"
+msgstr "使用全域通知設定"
+
+msgid "View open merge request"
+msgstr "查看此分支的合併請求 (merge request)"
+
+msgid "VisibilityLevel|Internal"
+msgstr "內部"
+
+msgid "VisibilityLevel|Private"
+msgstr "私有"
+
+msgid "VisibilityLevel|Public"
+msgstr "公開"
msgid "Want to see the data? Please ask an administrator for access."
msgstr "權限不足。如需查看相關資料,請向管理員申請權限。"
@@ -283,12 +1039,85 @@ msgstr "權限不足。如需查看相關資料,請向管理員申請權限。
msgid "We don't have enough data to show this stage."
msgstr "因該階段的資料不足而無法顯示相關資訊"
-msgid "You have reached your project limit"
+msgid "Withdraw Access Request"
+msgstr "取消權限申請"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"即將要刪除 %{project_name_with_namespace}。\n"
+"被刪除的專案完全無法救回來喔!\n"
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr ""
+"將要刪除本分支專案與主幹的所有關聯 (fork relationship) 。 %{forked_from_project} "
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「100%確定」要這麼做嗎?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "只能在分支 (branch) 上建立檔案"
+
+msgid "You have reached your project limit"
+msgstr "您已達到專案數量限制"
+
+msgid "You must sign in to star a project"
+msgstr "必須登入才能收藏專案"
msgid "You need permission."
-msgstr "您需要相關的權限。"
+msgstr "需要權限才能這麼做。"
+
+msgid "You will not get any notifications via email"
+msgstr "不會收到任何通知郵件"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "只接收您選擇的事件通知"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "只接收參與主題的通知"
+
+msgid "You will receive notifications for any activity"
+msgstr "接收所有活動的通知"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "只接收評論中提及(@)您的通知"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"在帳號上 %{set_password_link} 之前, 將無法使用 %{protocol} 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "在個人帳號中 %{add_ssh_key_link} 之前, 將無法使用 SSH 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid "Your name"
+msgstr "您的名字"
msgid "day"
msgid_plural "days"
msgstr[0] "天"
+
+msgid "new merge request"
+msgstr "建立合併請求"
+
+msgid "notification emails"
+msgstr "通知信"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "上層"
+
diff --git a/package.json b/package.json
index 045f07ee2f9..5a997e813f8 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"babel-core": "^6.22.1",
+ "babel-eslint": "^7.2.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-define": "^1.2.0",
"babel-preset-latest": "^6.24.0",
diff --git a/qa/qa.rb b/qa/qa.rb
index 58cf615cc9f..bdfb8237995 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -49,7 +49,6 @@ module QA
autoload :Entry, 'qa/page/main/entry'
autoload :Menu, 'qa/page/main/menu'
autoload :Groups, 'qa/page/main/groups'
- autoload :Projects, 'qa/page/main/projects'
end
module Project
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 7ce4e9009f5..f7c2086d0dd 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -14,6 +14,13 @@ module QA
within_user_menu { click_link 'Admin area' }
end
+ def go_to_new_project
+ within_user_menu do
+ find('.header-new-dropdown-toggle').click
+ click_link('New project')
+ end
+ end
+
def sign_out
within_user_menu do
find('.header-user-dropdown-toggle').click
diff --git a/qa/qa/page/main/projects.rb b/qa/qa/page/main/projects.rb
deleted file mode 100644
index 28d3a424022..00000000000
--- a/qa/qa/page/main/projects.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module QA
- module Page
- module Main
- class Projects < Page::Base
- def go_to_new_project
- ##
- # There are 'New Project' and 'New project' buttons on the projects
- # page, so we can't use `click_on`.
- #
- button = find('a', text: /^new project$/i)
- button.click
- end
- end
- end
- end
-end
diff --git a/qa/qa/scenario/gitlab/project/create.rb b/qa/qa/scenario/gitlab/project/create.rb
index 38522714e64..99d0fc42a94 100644
--- a/qa/qa/scenario/gitlab/project/create.rb
+++ b/qa/qa/scenario/gitlab/project/create.rb
@@ -14,8 +14,7 @@ module QA
def perform
Page::Main::Menu.act { go_to_groups }
Page::Main::Groups.act { prepare_test_namespace }
- Page::Main::Menu.act { go_to_projects }
- Page::Main::Projects.act { go_to_new_project }
+ Page::Main::Menu.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
index b341aa3094a..4dfdd6cd93c 100644
--- a/qa/qa/specs/config.rb
+++ b/qa/qa/specs/config.rb
@@ -25,27 +25,15 @@ module QA
def configure_rspec!
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
- # This option will default to `true` in RSpec 4. It makes the `description`
- # and `failure_message` of custom matchers include text for helper methods
- # defined using `chain`.
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
- # Prevents you from mocking or stubbing a method that does not exist on
- # a real object. This is generally recommended, and will default to
- # `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
- # Run specs in random order to surface order dependencies.
config.order = :random
Kernel.srand config.seed
-
- # config.before(:all) do
- # page.current_window.resize_to(1200, 1800)
- # end
-
config.formatter = :documentation
config.color = true
end
@@ -56,7 +44,7 @@ module QA
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
'chromeOptions' => {
'binary' => '/usr/bin/google-chrome-stable',
- 'args' => %w[headless no-sandbox disable-gpu]
+ 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1024]
}
)
@@ -64,6 +52,10 @@ module QA
.new(app, browser: :chrome, desired_capabilities: capabilities)
end
+ Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+ end
+
Capybara.configure do |config|
config.app_host = @address
config.default_driver = :chrome
diff --git a/rubocop/cop/active_record_dependent.rb b/rubocop/cop/active_record_dependent.rb
new file mode 100644
index 00000000000..8d15f150885
--- /dev/null
+++ b/rubocop/cop/active_record_dependent.rb
@@ -0,0 +1,26 @@
+require_relative '../model_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that prevents the use of `dependent: ...` in ActiveRecord models.
+ class ActiveRecordDependent < RuboCop::Cop::Cop
+ include ModelHelpers
+
+ MSG = 'Do not use `dependent: to remove associated data, ' \
+ 'use foreign keys with cascading deletes instead'.freeze
+
+ METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze
+
+ def on_send(node)
+ return unless in_model?(node)
+ return unless METHOD_NAMES.include?(node.children[1])
+
+ node.children.last.each_node(:pair) do |pair|
+ key_name = pair.children[0].children[0]
+
+ add_offense(pair, :expression) if key_name == :dependent
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/activerecord_serialize.rb b/rubocop/cop/active_record_serialize.rb
index 9bdcc3b4c34..204caf37f8b 100644
--- a/rubocop/cop/activerecord_serialize.rb
+++ b/rubocop/cop/active_record_serialize.rb
@@ -3,7 +3,7 @@ require_relative '../model_helpers'
module RuboCop
module Cop
# Cop that prevents the use of `serialize` in ActiveRecord models.
- class ActiverecordSerialize < RuboCop::Cop::Cop
+ class ActiveRecordSerialize < RuboCop::Cop::Cop
include ModelHelpers
MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze
diff --git a/rubocop/cop/in_batches.rb b/rubocop/cop/in_batches.rb
new file mode 100644
index 00000000000..c0240187e66
--- /dev/null
+++ b/rubocop/cop/in_batches.rb
@@ -0,0 +1,16 @@
+require_relative '../model_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that prevents the use of `in_batches`
+ class InBatches < RuboCop::Cop::Cop
+ MSG = 'Do not use `in_batches`, use `each_batch` from the EachBatch module instead'.freeze
+
+ def on_send(node)
+ return unless node.children[1] == :in_batches
+
+ add_offense(node, :selector)
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/project_path_helper.rb b/rubocop/cop/project_path_helper.rb
new file mode 100644
index 00000000000..3e1ce71ac06
--- /dev/null
+++ b/rubocop/cop/project_path_helper.rb
@@ -0,0 +1,51 @@
+module RuboCop
+ module Cop
+ class ProjectPathHelper < RuboCop::Cop::Cop
+ MSG = 'Use short project path helpers without explicitly passing the namespace: ' \
+ '`foo_project_bar_path(project, bar)` instead of ' \
+ '`foo_namespace_project_bar_path(project.namespace, project, bar)`.'.freeze
+
+ METHOD_NAME_PATTERN = /\A([a-z_]+_)?namespace_project(?:_[a-z_]+)?_(?:url|path)\z/.freeze
+
+ def on_send(node)
+ return unless method_name(node).to_s =~ METHOD_NAME_PATTERN
+
+ namespace_expr, project_expr = arguments(node)
+ return unless namespace_expr && project_expr
+
+ return unless namespace_expr.type == :send
+ return unless method_name(namespace_expr) == :namespace
+ return unless receiver(namespace_expr) == project_expr
+
+ add_offense(node, :selector)
+ end
+
+ def autocorrect(node)
+ helper_name = method_name(node).to_s.sub('namespace_project', 'project')
+
+ arguments = arguments(node)
+ arguments.shift # Remove namespace argument
+
+ replacement = "#{helper_name}(#{arguments.map(&:source).join(', ')})"
+
+ lambda do |corrector|
+ corrector.replace(node.source_range, replacement)
+ end
+ end
+
+ private
+
+ def receiver(node)
+ node.children[0]
+ end
+
+ def method_name(node)
+ node.children[1]
+ end
+
+ def arguments(node)
+ node.children[2..-1]
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 55d7708fa8c..f76144275c9 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,8 +1,11 @@
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
-require_relative 'cop/activerecord_serialize'
+require_relative 'cop/active_record_serialize'
require_relative 'cop/redirect_with_status'
require_relative 'cop/polymorphic_associations'
+require_relative 'cop/project_path_helper'
+require_relative 'cop/active_record_dependent'
+require_relative 'cop/in_batches'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 80a418feb3e..ada011e7595 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -13,6 +13,31 @@ describe AbuseReportsController do
sign_in(reporter)
end
+ describe 'GET new' do
+ context 'when the user has already been deleted' do
+ it 'redirects the reporter to root_path' do
+ user_id = user.id
+ user.destroy
+
+ get :new, { user_id: user_id }
+
+ expect(response).to redirect_to root_path
+ expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.')
+ end
+ end
+
+ context 'when the user has already been blocked' do
+ it 'redirects the reporter to the user\'s profile' do
+ user.block
+
+ get :new, { user_id: user.id }
+
+ expect(response).to redirect_to user
+ expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.')
+ end
+ end
+ end
+
describe 'POST create' do
context 'with valid attributes' do
it 'saves the abuse report' do
diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb
new file mode 100644
index 00000000000..2b63933008f
--- /dev/null
+++ b/spec/controllers/dashboard/labels_controller_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Dashboard::LabelsController do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let!(:label) { create(:label, project: project) }
+
+ before do
+ sign_in(user)
+ project.add_reporter(user)
+ end
+
+ describe "#index" do
+ let!(:unrelated_label) { create(:label, project: create(:empty_project, :public)) }
+
+ it 'returns global labels for projects the user has a relationship with' do
+ get :index, format: :json
+
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]["id"]).to be_nil
+ expect(json_response[0]["title"]).to eq(label.title)
+ end
+ end
+end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index f3263bc177d..aad67dd0164 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Groups::MilestonesController do
let(:group) { create(:group) }
- let(:project) { create(:empty_project, group: group) }
- let(:project2) { create(:empty_project, group: group) }
+ let!(:project) { create(:empty_project, group: group) }
+ let!(:project2) { create(:empty_project, group: group) }
let(:user) { create(:user) }
let(:title) { '肯定不是中文的问题' }
let(:milestone) do
@@ -17,28 +17,127 @@ describe Groups::MilestonesController do
end
let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
+ let(:milestone_params) do
+ {
+ title: title,
+ start_date: Date.today,
+ due_date: 1.month.from_now.to_date
+ }
+ end
+
before do
sign_in(user)
group.add_owner(user)
project.team << [user, :master]
end
+ describe '#index' do
+ it 'shows group milestones page' do
+ get :index, group_id: group.to_param
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'as JSON' do
+ let!(:milestone) { create(:milestone, group: group, title: 'group milestone') }
+ let!(:legacy_milestone1) { create(:milestone, project: project, title: 'legacy') }
+ let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') }
+
+ it 'lists legacy group milestones and group milestones' do
+ get :index, group_id: group.to_param, format: :json
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones.first["title"]).to eq("group milestone")
+ expect(milestones.second["title"]).to eq("legacy")
+ expect(response).to have_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+ end
+ end
+ end
+
+ describe '#show' do
+ let(:milestone1) { create(:milestone, project: project, title: 'legacy') }
+ let(:milestone2) { create(:milestone, project: project, title: 'legacy') }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ context 'when there is a title parameter' do
+ it 'searchs for a legacy group milestone' do
+ expect(GlobalMilestone).to receive(:build)
+ expect(Milestone).not_to receive(:find_by_iid)
+
+ get :show, group_id: group.to_param, id: title, title: milestone1.safe_title
+ end
+ end
+
+ context 'when there is not a title parameter' do
+ it 'searchs for a group milestone' do
+ expect(GlobalMilestone).not_to receive(:build)
+ expect(Milestone).to receive(:find_by_iid)
+
+ get :show, group_id: group.to_param, id: group_milestone.id
+ end
+ end
+ end
+
it_behaves_like 'milestone tabs'
describe "#create" do
it "creates group milestone with Chinese title" do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: milestone_params
+
+ milestone = Milestone.find_by_title(title)
+
+ expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
+ expect(milestone.group_id).to eq(group.id)
+ expect(milestone.due_date).to eq(milestone_params[:due_date])
+ expect(milestone.start_date).to eq(milestone_params[:start_date])
+ end
+ end
+
+ describe "#update" do
+ let(:milestone) { create(:milestone, group: group) }
+
+ it "updates group milestone" do
+ milestone_params[:title] = "title changed"
+
+ put :update,
+ id: milestone.iid,
+ group_id: group.to_param,
+ milestone: milestone_params
- expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
- expect(Milestone.where(title: title).count).to eq(2)
+ milestone.reload
+ expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
+ expect(milestone.title).to eq("title changed")
end
- it "redirects to new when there are no project ids" do
- post :create, group_id: group.to_param, milestone: { title: title, project_ids: [""] }
- expect(response).to render_template :new
- expect(assigns(:milestone).errors).not_to be_nil
+ context "legacy group milestones" do
+ let!(:milestone1) { create(:milestone, project: project, title: 'legacy milestone', description: "old description") }
+ let!(:milestone2) { create(:milestone, project: project2, title: 'legacy milestone', description: "old description") }
+
+ it "updates only group milestones state" do
+ milestone_params[:title] = "title changed"
+ milestone_params[:description] = "description changed"
+ milestone_params[:state_event] = "close"
+
+ put :update,
+ id: milestone1.title.to_slug.to_s,
+ group_id: group.to_param,
+ milestone: milestone_params,
+ title: milestone1.title
+
+ expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title))
+
+ [milestone1, milestone2].each do |milestone|
+ milestone.reload
+ expect(milestone.title).to eq("legacy milestone")
+ expect(milestone.description).to eq("old description")
+ expect(milestone.state).to eq("closed")
+ end
+ end
end
end
@@ -141,7 +240,7 @@ describe Groups::MilestonesController do
it 'does not 404' do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).not_to have_http_status(404)
end
@@ -149,7 +248,7 @@ describe Groups::MilestonesController do
it 'does not redirect to the correct casing' do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).not_to have_http_status(301)
end
@@ -161,7 +260,7 @@ describe Groups::MilestonesController do
it 'returns not found' do
post :create,
group_id: redirect_route.path,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
new file mode 100644
index 00000000000..2e0efb57c74
--- /dev/null
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Groups::Settings::CiCdController do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ group.add_master(user)
+ sign_in(user)
+ end
+
+ describe 'GET #show' do
+ it 'renders show with 200 status code' do
+ get :show, group_id: group
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+end
diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb
new file mode 100644
index 00000000000..02f2fa46047
--- /dev/null
+++ b/spec/controllers/groups/variables_controller_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Groups::VariablesController do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ group.add_master(user)
+ end
+
+ describe 'POST #create' do
+ context 'variable is valid' do
+ it 'shows a success flash message' do
+ post :create, group_id: group, variable: { key: "one", value: "two" }
+
+ expect(flash[:notice]).to include 'Variable was successfully created.'
+ expect(response).to redirect_to(group_settings_ci_cd_path(group))
+ end
+ end
+
+ context 'variable is invalid' do
+ it 'renders show' do
+ post :create, group_id: group, variable: { key: "..one", value: "two" }
+
+ expect(response).to render_template("groups/variables/show")
+ end
+ end
+ end
+
+ describe 'POST #update' do
+ let(:variable) { create(:ci_group_variable) }
+
+ context 'updating a variable with valid characters' do
+ before do
+ group.variables << variable
+ end
+
+ it 'shows a success flash message' do
+ post :update, group_id: group,
+ id: variable.id, variable: { key: variable.key, value: 'two' }
+
+ expect(flash[:notice]).to include 'Variable was successfully updated.'
+ expect(response).to redirect_to(group_variables_path(group))
+ end
+
+ it 'renders the action #show if the variable key is invalid' do
+ post :update, group_id: group,
+ id: variable.id, variable: { key: '?', value: variable.value }
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template :show
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 428bc45b842..d2c613a2423 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -134,10 +134,7 @@ describe Projects::ArtifactsController do
context 'found the job and redirect' do
shared_examples 'redirect to the job' do
it 'redirects' do
- path = browse_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ path = browse_project_job_artifacts_path(project, job)
expect(response).to redirect_to(path)
end
@@ -174,11 +171,7 @@ describe Projects::ArtifactsController do
end
it 'redirects' do
- path = file_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job,
- 'README.md')
+ path = file_project_job_artifacts_path(project, job, 'README.md')
expect(response).to redirect_to(path)
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index c20cf6a4291..02bbc48dc59 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -117,7 +117,7 @@ describe Projects::BlobController do
end
it 'redirects to blob show' do
- expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG'))
+ expect(response).to redirect_to(project_blob_path(project, 'master/CHANGELOG'))
end
end
@@ -164,7 +164,7 @@ describe Projects::BlobController do
end
def blob_after_edit_path
- namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')
+ project_blob_path(project, 'master/CHANGELOG')
end
before do
@@ -186,7 +186,7 @@ describe Projects::BlobController do
it 'redirects to MR diff' do
put :update, mr_params
- after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ after_edit_path = diffs_project_merge_request_path(project, merge_request)
file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}"
expect(response).to redirect_to(after_edit_path + file_anchor)
end
@@ -223,7 +223,7 @@ describe Projects::BlobController do
it 'redirects to blob' do
put :update, default_params
- expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG'))
+ expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG'))
end
end
@@ -235,8 +235,7 @@ describe Projects::BlobController do
put :update, default_params
expect(response).to redirect_to(
- new_namespace_project_merge_request_path(
- forked_project.namespace,
+ project_new_merge_request_path(
forked_project,
merge_request: {
source_project_id: forked_project.id,
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 14426b09c73..9cd4e9dbf84 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -110,7 +110,7 @@ describe Projects::BranchesController do
branch_name: branch,
issue_iid: issue.iid
- expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch)
+ expect(response).to redirect_to project_tree_path(project, branch)
end
it 'redirects to autodeploy setup page' do
@@ -127,7 +127,7 @@ describe Projects::BranchesController do
branch_name: branch,
issue_iid: issue.iid
- expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch))
+ expect(response.location).to include(project_new_blob_path(project, branch))
expect(response).to have_http_status(302)
end
end
@@ -303,7 +303,7 @@ describe Projects::BranchesController do
it 'redirects to branches path' do
expect(response)
- .to redirect_to(namespace_project_branches_path(project.namespace, project))
+ .to redirect_to(project_branches_path(project))
end
end
end
@@ -323,7 +323,7 @@ describe Projects::BranchesController do
it 'redirects to branches' do
destroy_all_merged
- expect(response).to redirect_to namespace_project_branches_path(project.namespace, project)
+ expect(response).to redirect_to project_branches_path(project)
end
it 'starts worker to delete merged branches' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index e10da40eaab..eb61a0c080c 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -169,7 +169,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: commit.id)
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully reverted.')
end
end
@@ -191,7 +191,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: commit.id)
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(response).to redirect_to project_commit_path(project, commit.id)
expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
end
end
@@ -218,7 +218,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: master_pickable_commit.id)
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
end
end
@@ -240,7 +240,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: master_pickable_commit.id)
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id)
expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 8f4694c9854..b4f9fd9b7a2 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -72,7 +72,7 @@ describe Projects::CompareController do
from: '',
to: 'master')
- expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master'))
+ expect(response).to redirect_to(project_compare_index_path(project, to: 'master'))
end
it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
@@ -82,7 +82,7 @@ describe Projects::CompareController do
from: 'master',
to: '')
- expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master'))
+ expect(response).to redirect_to(project_compare_index_path(project, from: 'master'))
end
it 'redirects back to index when params[:from] and params[:to] are empty' do
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index ad0b046742d..f88f50c3cc6 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -58,11 +58,9 @@ describe Projects::EnvironmentsController do
expect(json_response['stopped_count']).to eq 1
end
- it 'does not set the polling interval header' do
- # TODO, this is a temporary fix, see follow up issue:
- # https://gitlab.com/gitlab-org/gitlab-ee/issues/2677
+ it 'sets the polling interval header' do
expect(response).to have_http_status(:ok)
- expect(response.headers['Poll-Interval']).to be_nil
+ expect(response.headers['Poll-Interval']).to eq("3000")
end
end
@@ -184,7 +182,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- namespace_project_job_url(project.namespace, project, action) })
+ project_job_url(project, action) })
end
end
@@ -198,7 +196,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- namespace_project_environment_url(project.namespace, project, environment) })
+ project_environment_url(project, environment) })
end
end
end
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index b5435357f53..019a50882ab 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -34,7 +34,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
@@ -65,7 +65,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
@@ -79,7 +79,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(flash[:alert]).to eq('Please select a group.')
end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 6724b474179..9be61342616 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -59,7 +59,7 @@ describe Projects::ImportsController do
it 'redirects to new_namespace_project_import_path' do
get :show, namespace_id: project.namespace.to_param, project_id: project
- expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+ expect(response).to redirect_to new_project_import_path(project)
end
end
@@ -75,7 +75,7 @@ describe Projects::ImportsController do
get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully forked.'
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
@@ -84,14 +84,14 @@ describe Projects::ImportsController do
get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully imported.'
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
context 'when continue params is present' do
let(:params) do
{
- to: namespace_project_path(project.namespace, project),
+ to: project_path(project),
notice: 'Finished'
}
end
@@ -120,7 +120,7 @@ describe Projects::ImportsController do
it 'redirects to namespace_project_path' do
get :show, namespace_id: project.namespace.to_param, project_id: project
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 9f98427a86b..22aad0b3225 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -35,7 +35,7 @@ describe Projects::IssuesController do
it "returns 301 if request path doesn't match project path" do
get :index, namespace_id: project.namespace, project_id: project.path.upcase
- expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+ expect(response).to redirect_to(project_issues_path(project))
end
it "returns 404 when issues are disabled" do
@@ -329,7 +329,7 @@ describe Projects::IssuesController do
update_verified_issue
expect(response)
- .to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+ .to redirect_to(project_issue_path(project, issue))
end
it 'accepts an issue after recaptcha is verified' do
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index bf1776eb320..f19ad4c2c81 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -178,7 +178,7 @@ describe Projects::LabelsController do
it 'redirects to the correct casing' do
get :index, namespace_id: project.namespace, project_id: project.to_param.upcase
- expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+ expect(response).to redirect_to(project_labels_path(project))
expect(controller).not_to set_flash[:notice]
end
end
@@ -191,7 +191,7 @@ describe Projects::LabelsController do
it 'redirects to the canonical path' do
get :index, namespace_id: project.namespace, project_id: project.to_param + 'old'
- expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+ expect(response).to redirect_to(project_labels_path(project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project))
end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 422a8b6fac0..12e413db902 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -38,7 +38,7 @@ describe Projects::MattermostsController do
it 'shows the error' do
allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"])
- expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project))
+ expect(subject).to redirect_to(new_project_mattermost_url(project))
end
end
@@ -51,7 +51,7 @@ describe Projects::MattermostsController do
subject
service = project.services.last
- expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service))
+ expect(subject).to redirect_to(edit_project_service_url(project, service))
end
end
end
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
new file mode 100644
index 00000000000..9278ac8edd8
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -0,0 +1,307 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::ConflictsController do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+ let(:merge_request_with_conflicts) do
+ create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
+ mr.mark_as_unmergeable
+ end
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET show' do
+ context 'when the conflicts cannot be resolved in the UI' do
+ before do
+ allow_any_instance_of(Gitlab::Conflict::Parser)
+ .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
+
+ get :show,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ format: 'json'
+ end
+
+ it 'returns a 200 status code' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns JSON with a message' do
+ expect(json_response.keys).to contain_exactly('message', 'type')
+ end
+ end
+
+ context 'with valid conflicts' do
+ before do
+ get :show,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ format: 'json'
+ end
+
+ it 'matches the schema' do
+ expect(response).to match_response_schema('conflicts')
+ end
+
+ it 'includes meta info about the MR' do
+ expect(json_response['commit_message']).to include('Merge branch')
+ expect(json_response['commit_sha']).to match(/\h{40}/)
+ expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
+ expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
+ end
+
+ it 'includes each file that has conflicts' do
+ filenames = json_response['files'].map { |file| file['new_path'] }
+
+ expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
+ end
+
+ it 'splits files into sections with lines' do
+ json_response['files'].each do |file|
+ file['sections'].each do |section|
+ expect(section).to include('conflict', 'lines')
+
+ section['lines'].each do |line|
+ if section['conflict']
+ expect(line['type']).to be_in(%w(old new))
+ expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
+ else
+ if line['type'].nil?
+ expect(line['old_line']).not_to eq(nil)
+ expect(line['new_line']).not_to eq(nil)
+ else
+ expect(line['type']).to eq('match')
+ expect(line['old_line']).to eq(nil)
+ expect(line['new_line']).to eq(nil)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it 'has unique section IDs across files' do
+ section_ids = json_response['files'].flat_map do |file|
+ file['sections'].map { |section| section['id'] }.compact
+ end
+
+ expect(section_ids.uniq).to eq(section_ids)
+ end
+ end
+ end
+
+ describe 'GET conflict_for_path' do
+ def conflict_for_path(path)
+ get :conflict_for_path,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ old_path: path,
+ new_path: path,
+ format: 'json'
+ end
+
+ context 'when the conflicts cannot be resolved in the UI' do
+ before do
+ allow_any_instance_of(Gitlab::Conflict::Parser)
+ .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
+
+ conflict_for_path('files/ruby/regex.rb')
+ end
+
+ it 'returns a 404 status code' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'when the file does not exist cannot be resolved in the UI' do
+ before do
+ conflict_for_path('files/ruby/regexp.rb')
+ end
+
+ it 'returns a 404 status code' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'with an existing file' do
+ let(:path) { 'files/ruby/regex.rb' }
+
+ before do
+ conflict_for_path(path)
+ end
+
+ it 'returns a 200 status code' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the file in JSON format' do
+ content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
+ .file_for_path(path, path)
+ .content
+
+ expect(json_response).to include('old_path' => path,
+ 'new_path' => path,
+ 'blob_icon' => 'file-text-o',
+ 'blob_path' => a_string_ending_with(path),
+ 'blob_ace_mode' => 'ruby',
+ 'content' => content)
+ end
+ end
+ end
+
+ context 'POST resolve_conflicts' do
+ let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
+
+ def resolve_conflicts(files)
+ post :resolve_conflicts,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ format: 'json',
+ files: files,
+ commit_message: 'Commit message'
+ end
+
+ context 'with valid params' do
+ before do
+ resolved_files = [
+ {
+ 'new_path' => 'files/ruby/popen.rb',
+ 'old_path' => 'files/ruby/popen.rb',
+ 'sections' => {
+ '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+ }
+ }, {
+ 'new_path' => 'files/ruby/regex.rb',
+ 'old_path' => 'files/ruby/regex.rb',
+ 'sections' => {
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+ }
+ }
+ ]
+
+ resolve_conflicts(resolved_files)
+ end
+
+ it 'creates a new commit on the branch' do
+ expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
+ expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
+ end
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ context 'when sections are missing' do
+ before do
+ resolved_files = [
+ {
+ 'new_path' => 'files/ruby/popen.rb',
+ 'old_path' => 'files/ruby/popen.rb',
+ 'sections' => {
+ '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+ }
+ }, {
+ 'new_path' => 'files/ruby/regex.rb',
+ 'old_path' => 'files/ruby/regex.rb',
+ 'sections' => {
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
+ }
+ }
+ ]
+
+ resolve_conflicts(resolved_files)
+ end
+
+ it 'returns a 400 error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'has a message with the name of the first missing section' do
+ expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
+ end
+
+ it 'does not create a new commit' do
+ expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+ end
+ end
+
+ context 'when files are missing' do
+ before do
+ resolved_files = [
+ {
+ 'new_path' => 'files/ruby/regex.rb',
+ 'old_path' => 'files/ruby/regex.rb',
+ 'sections' => {
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+ }
+ }
+ ]
+
+ resolve_conflicts(resolved_files)
+ end
+
+ it 'returns a 400 error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'has a message with the name of the missing file' do
+ expect(json_response['message']).to include('files/ruby/popen.rb')
+ end
+
+ it 'does not create a new commit' do
+ expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+ end
+ end
+
+ context 'when a file has identical content to the conflict' do
+ before do
+ content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
+ .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb')
+ .content
+
+ resolved_files = [
+ {
+ 'new_path' => 'files/ruby/popen.rb',
+ 'old_path' => 'files/ruby/popen.rb',
+ 'content' => content
+ }, {
+ 'new_path' => 'files/ruby/regex.rb',
+ 'old_path' => 'files/ruby/regex.rb',
+ 'sections' => {
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+ }
+ }
+ ]
+
+ resolve_conflicts(resolved_files)
+ end
+
+ it 'returns a 400 error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'has a message with the path of the problem file' do
+ expect(json_response['message']).to include('files/ruby/popen.rb')
+ end
+
+ it 'does not create a new commit' do
+ expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
new file mode 100644
index 00000000000..f9d8f0f5fcf
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::CreationsController do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+
+ before do
+ fork_project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ describe 'GET new' do
+ context 'merge request that removes a submodule' do
+ render_views
+
+ it 'renders new merge request widget template' do
+ get :new,
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ }
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe 'GET pipelines' do
+ before do
+ create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id,
+ ref: 'remove-submodule',
+ project: fork_project)
+ end
+
+ it 'renders JSON including serialized pipelines' do
+ get :pipelines,
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ },
+ format: :json
+
+ expect(response).to be_ok
+ expect(json_response).to have_key 'pipelines'
+ expect(json_response['pipelines']).not_to be_empty
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ format: 'json'
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when both branches are in the same project' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the source branch is in a different project to the target' do
+ let(:other_project) { create(:project) }
+
+ before do
+ other_project.team << [user, :master]
+ end
+
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before do
+ diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
new file mode 100644
index 00000000000..53fe2bdb189
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -0,0 +1,160 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::DiffsController do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET show' do
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :show, params.merge(extra_params)
+ end
+
+ context 'with default params' do
+ context 'for the same project' do
+ before do
+ go
+ end
+
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/diffs/_diffs')
+ expect(json_response).to have_key('html')
+ end
+ end
+
+ context 'with forked projects with submodules' do
+ render_views
+
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ merge_request.reload
+ go
+ end
+
+ it 'renders' do
+ expect(response).to be_success
+ expect(response.body).to have_content('Subproject commit')
+ end
+ end
+ end
+
+ context 'with ignore_whitespace_change' do
+ before do
+ go(w: 1)
+ end
+
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/diffs/_diffs')
+ expect(json_response).to have_key('html')
+ end
+ end
+
+ context 'with view' do
+ before do
+ go(view: 'parallel')
+ end
+
+ it 'saves the preferred diff view in a cookie' do
+ expect(response.cookies['diff_view']).to eq('parallel')
+ end
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { 'files/ruby/popen.rb' }
+
+ context 'when the merge request exists' do
+ context 'when the user can view the merge request' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before do
+ diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb')
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user cannot view the merge request' do
+ before do
+ project.team.truncate
+ diff_for_path(old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the merge request does not exist' do
+ before do
+ diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the merge request belongs to a different project' do
+ let(:other_project) { create(:empty_project) }
+
+ before do
+ other_project.team << [user, :master]
+ diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 6817c2652fd..6f9ce60cf75 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -14,53 +14,6 @@ describe Projects::MergeRequestsController do
sign_in(user)
end
- describe 'GET new' do
- context 'merge request that removes a submodule' do
- render_views
-
- let(:fork_project) { create(:forked_project_with_submodules) }
-
- before do
- fork_project.team << [user, :master]
- end
-
- context 'when rendering HTML response' do
- it 'renders new merge request widget template' do
- submit_new_merge_request
-
- expect(response).to be_success
- end
- end
-
- context 'when rendering JSON response' do
- before do
- create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id,
- ref: 'remove-submodule',
- project: fork_project)
- end
-
- it 'renders JSON including serialized pipelines' do
- submit_new_merge_request(format: :json)
-
- expect(response).to be_ok
- expect(json_response).to have_key 'pipelines'
- expect(json_response['pipelines']).not_to be_empty
- end
- end
- end
-
- def submit_new_merge_request(format: :html)
- get :new,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project,
- merge_request: {
- source_branch: 'remove-submodule',
- target_branch: 'master'
- },
- format: format
- end
- end
-
describe 'GET commit_change_content' do
it 'renders commit_change_content template' do
get :commit_change_content,
@@ -497,234 +450,6 @@ describe Projects::MergeRequestsController do
end
end
- describe 'GET diffs' do
- def go(extra_params = {})
- params = {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid
- }
-
- get :diffs, params.merge(extra_params)
- end
-
- it_behaves_like "loads labels", :diffs
-
- context 'with default params' do
- context 'as html' do
- before do
- go(format: 'html')
- end
-
- it 'renders the diff template' do
- expect(response).to render_template('diffs')
- end
- end
-
- context 'as json' do
- before do
- go(format: 'json')
- end
-
- it 'renders the diffs template to a string' do
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(json_response).to have_key('html')
- end
- end
-
- context 'with forked projects with submodules' do
- render_views
-
- let(:project) { create(:project) }
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
-
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- merge_request.reload
- go(format: 'json')
- end
-
- it 'renders' do
- expect(response).to be_success
- expect(response.body).to have_content('Subproject commit')
- end
- end
- end
-
- context 'with ignore_whitespace_change' do
- context 'as html' do
- before do
- go(format: 'html', w: 1)
- end
-
- it 'renders the diff template' do
- expect(response).to render_template('diffs')
- end
- end
-
- context 'as json' do
- before do
- go(format: 'json', w: 1)
- end
-
- it 'renders the diffs template to a string' do
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(json_response).to have_key('html')
- end
- end
- end
-
- context 'with view' do
- before do
- go(view: 'parallel')
- end
-
- it 'saves the preferred diff view in a cookie' do
- expect(response.cookies['diff_view']).to eq('parallel')
- end
- end
- end
-
- describe 'GET diff_for_path' do
- def diff_for_path(extra_params = {})
- params = {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- get :diff_for_path, params.merge(extra_params)
- end
-
- context 'when an ID param is passed' do
- let(:existing_path) { 'files/ruby/popen.rb' }
-
- context 'when the merge request exists' do
- context 'when the user can view the merge request' do
- context 'when the path exists in the diff' do
- it 'enables diff notes' do
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
-
- expect(assigns(:diff_notes_disabled)).to be_falsey
- expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
- noteable_id: merge_request.id)
- end
-
- it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
- expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs)
- end
-
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
- end
- end
-
- context 'when the path does not exist in the diff' do
- before do
- diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb')
- end
-
- it 'returns a 404' do
- expect(response).to have_http_status(404)
- end
- end
- end
-
- context 'when the user cannot view the merge request' do
- before do
- project.team.truncate
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
- end
-
- it 'returns a 404' do
- expect(response).to have_http_status(404)
- end
- end
- end
-
- context 'when the merge request does not exist' do
- before do
- diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path)
- end
-
- it 'returns a 404' do
- expect(response).to have_http_status(404)
- end
- end
-
- context 'when the merge request belongs to a different project' do
- let(:other_project) { create(:empty_project) }
-
- before do
- other_project.team << [user, :master]
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project)
- end
-
- it 'returns a 404' do
- expect(response).to have_http_status(404)
- end
- end
- end
-
- context 'when source and target params are passed' do
- let(:existing_path) { 'files/ruby/feature.rb' }
-
- context 'when both branches are in the same project' do
- it 'disables diff notes' do
- diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
-
- expect(assigns(:diff_notes_disabled)).to be_truthy
- end
-
- it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
- expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs)
- end
-
- diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
- end
- end
-
- context 'when the source branch is in a different project to the target' do
- let(:other_project) { create(:project) }
-
- before do
- other_project.team << [user, :master]
- end
-
- context 'when the path exists in the diff' do
- it 'disables diff notes' do
- diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
-
- expect(assigns(:diff_notes_disabled)).to be_truthy
- end
-
- it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
- expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs)
- end
-
- diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
- end
- end
-
- context 'when the path does not exist in the diff' do
- before do
- diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
- end
-
- it 'returns a 404' do
- expect(response).to have_http_status(404)
- end
- end
- end
- end
- end
-
describe 'GET commits' do
def go(format: 'html')
get :commits,
@@ -734,23 +459,11 @@ describe Projects::MergeRequestsController do
format: format
end
- it_behaves_like "loads labels", :commits
+ it 'renders the commits template to a string' do
+ go format: 'json'
- context 'as html' do
- it 'renders the show template' do
- go
-
- expect(response).to render_template('show')
- end
- end
-
- context 'as json' do
- it 'renders the commits template to a string' do
- go format: 'json'
-
- expect(response).to render_template('projects/merge_requests/show/_commits')
- expect(json_response).to have_key('html')
- end
+ expect(response).to render_template('projects/merge_requests/_commits')
+ expect(json_response).to have_key('html')
end
end
@@ -759,106 +472,16 @@ describe Projects::MergeRequestsController do
create(:ci_pipeline, project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
- end
-
- context 'when using HTML format' do
- it_behaves_like "loads labels", :pipelines
- end
- context 'when using JSON format' do
- before do
- get :pipelines,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
- format: :json
- end
-
- it 'responds with serialized pipelines' do
- expect(json_response).not_to be_empty
- end
- end
- end
-
- describe 'GET conflicts' do
- context 'when the conflicts cannot be resolved in the UI' do
- before do
- allow_any_instance_of(Gitlab::Conflict::Parser)
- .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
-
- get :conflicts,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- format: 'json'
- end
-
- it 'returns a 200 status code' do
- expect(response).to have_http_status(:ok)
- end
-
- it 'returns JSON with a message' do
- expect(json_response.keys).to contain_exactly('message', 'type')
- end
+ get :pipelines,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid,
+ format: :json
end
- context 'with valid conflicts' do
- before do
- get :conflicts,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- format: 'json'
- end
-
- it 'matches the schema' do
- expect(response).to match_response_schema('conflicts')
- end
-
- it 'includes meta info about the MR' do
- expect(json_response['commit_message']).to include('Merge branch')
- expect(json_response['commit_sha']).to match(/\h{40}/)
- expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
- expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
- end
-
- it 'includes each file that has conflicts' do
- filenames = json_response['files'].map { |file| file['new_path'] }
-
- expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
- end
-
- it 'splits files into sections with lines' do
- json_response['files'].each do |file|
- file['sections'].each do |section|
- expect(section).to include('conflict', 'lines')
-
- section['lines'].each do |line|
- if section['conflict']
- expect(line['type']).to be_in(%w(old new))
- expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
- else
- if line['type'].nil?
- expect(line['old_line']).not_to eq(nil)
- expect(line['new_line']).not_to eq(nil)
- else
- expect(line['type']).to eq('match')
- expect(line['old_line']).to eq(nil)
- expect(line['new_line']).to eq(nil)
- end
- end
- end
- end
- end
- end
-
- it 'has unique section IDs across files' do
- section_ids = json_response['files'].flat_map do |file|
- file['sections'].map { |section| section['id'] }.compact
- end
-
- expect(section_ids.uniq).to eq(section_ids)
- end
+ it 'responds with serialized pipelines' do
+ expect(json_response).not_to be_empty
end
end
@@ -913,215 +536,6 @@ describe Projects::MergeRequestsController do
end
end
- describe 'GET conflict_for_path' do
- def conflict_for_path(path)
- get :conflict_for_path,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- old_path: path,
- new_path: path,
- format: 'json'
- end
-
- context 'when the conflicts cannot be resolved in the UI' do
- before do
- allow_any_instance_of(Gitlab::Conflict::Parser)
- .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
-
- conflict_for_path('files/ruby/regex.rb')
- end
-
- it 'returns a 404 status code' do
- expect(response).to have_http_status(:not_found)
- end
- end
-
- context 'when the file does not exist cannot be resolved in the UI' do
- before do
- conflict_for_path('files/ruby/regexp.rb')
- end
-
- it 'returns a 404 status code' do
- expect(response).to have_http_status(:not_found)
- end
- end
-
- context 'with an existing file' do
- let(:path) { 'files/ruby/regex.rb' }
-
- before do
- conflict_for_path(path)
- end
-
- it 'returns a 200 status code' do
- expect(response).to have_http_status(:ok)
- end
-
- it 'returns the file in JSON format' do
- content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
- .file_for_path(path, path)
- .content
-
- expect(json_response).to include('old_path' => path,
- 'new_path' => path,
- 'blob_icon' => 'file-text-o',
- 'blob_path' => a_string_ending_with(path),
- 'blob_ace_mode' => 'ruby',
- 'content' => content)
- end
- end
- end
-
- context 'POST resolve_conflicts' do
- let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
-
- def resolve_conflicts(files)
- post :resolve_conflicts,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- format: 'json',
- files: files,
- commit_message: 'Commit message'
- end
-
- context 'with valid params' do
- before do
- resolved_files = [
- {
- 'new_path' => 'files/ruby/popen.rb',
- 'old_path' => 'files/ruby/popen.rb',
- 'sections' => {
- '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
- }
- }, {
- 'new_path' => 'files/ruby/regex.rb',
- 'old_path' => 'files/ruby/regex.rb',
- 'sections' => {
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
- }
- }
- ]
-
- resolve_conflicts(resolved_files)
- end
-
- it 'creates a new commit on the branch' do
- expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
- expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
- end
-
- it 'returns an OK response' do
- expect(response).to have_http_status(:ok)
- end
- end
-
- context 'when sections are missing' do
- before do
- resolved_files = [
- {
- 'new_path' => 'files/ruby/popen.rb',
- 'old_path' => 'files/ruby/popen.rb',
- 'sections' => {
- '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
- }
- }, {
- 'new_path' => 'files/ruby/regex.rb',
- 'old_path' => 'files/ruby/regex.rb',
- 'sections' => {
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
- }
- }
- ]
-
- resolve_conflicts(resolved_files)
- end
-
- it 'returns a 400 error' do
- expect(response).to have_http_status(:bad_request)
- end
-
- it 'has a message with the name of the first missing section' do
- expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
- end
-
- it 'does not create a new commit' do
- expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
- end
- end
-
- context 'when files are missing' do
- before do
- resolved_files = [
- {
- 'new_path' => 'files/ruby/regex.rb',
- 'old_path' => 'files/ruby/regex.rb',
- 'sections' => {
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
- }
- }
- ]
-
- resolve_conflicts(resolved_files)
- end
-
- it 'returns a 400 error' do
- expect(response).to have_http_status(:bad_request)
- end
-
- it 'has a message with the name of the missing file' do
- expect(json_response['message']).to include('files/ruby/popen.rb')
- end
-
- it 'does not create a new commit' do
- expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
- end
- end
-
- context 'when a file has identical content to the conflict' do
- before do
- content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
- .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb')
- .content
-
- resolved_files = [
- {
- 'new_path' => 'files/ruby/popen.rb',
- 'old_path' => 'files/ruby/popen.rb',
- 'content' => content
- }, {
- 'new_path' => 'files/ruby/regex.rb',
- 'old_path' => 'files/ruby/regex.rb',
- 'sections' => {
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
- '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
- }
- }
- ]
-
- resolve_conflicts(resolved_files)
- end
-
- it 'returns a 400 error' do
- expect(response).to have_http_status(:bad_request)
- end
-
- it 'has a message with the path of the problem file' do
- expect(json_response['message']).to include('files/ruby/popen.rb')
- end
-
- it 'does not create a new commit' do
- expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
- end
- end
- end
-
describe 'POST assign_related_issues' do
let(:issue1) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project) }
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 84a61b2784e..bb5a340cd96 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -31,6 +31,40 @@ describe Projects::MilestonesController do
end
end
+ describe "#index" do
+ context "as html" do
+ before do
+ get :index, namespace_id: project.namespace.id, project_id: project.id
+ end
+
+ it "queries only projects milestones" do
+ milestones = assigns(:milestones)
+
+ expect(milestones.count).to eq(1)
+ expect(milestones.where(project_id: nil)).to be_empty
+ end
+ end
+
+ context "as json" do
+ let!(:group) { create(:group, :public) }
+ let!(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+
+ before do
+ project.update(namespace: group)
+ get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ end
+
+ it "queries projects milestones and groups milestones" do
+ milestones = assigns(:milestones)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones.where(project_id: nil).first).to eq(group_milestone)
+ expect(milestones.where(group_id: nil).first).to eq(milestone)
+ end
+ end
+ end
+
describe "#destroy" do
it "removes milestone" do
expect(issue.milestone_id).to eq(milestone.id)
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 33853c4b9d0..920189be381 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -46,7 +46,7 @@ describe Projects::PagesDomainsController do
post(:create, request_params.merge(pages_domain: pages_domain_params))
end.to change { PagesDomain.count }.by(1)
- expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+ expect(response).to redirect_to(project_pages_path(project))
end
end
@@ -56,7 +56,7 @@ describe Projects::PagesDomainsController do
delete(:destroy, request_params.merge(id: pages_domain.domain))
end.to change { PagesDomain.count }.by(-1)
- expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+ expect(response).to redirect_to(project_pages_path(project))
end
end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index f8f95dd9bc8..41bf5580993 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::PipelineSchedulesController do
+ include AccessMatchersForController
+
set(:project) { create(:empty_project, :public) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
@@ -17,6 +19,14 @@ describe Projects::PipelineSchedulesController do
expect(response).to render_template(:index)
end
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new { visit_pipelines_schedules }.count
+
+ create_list(:ci_pipeline_schedule, 2, project: project)
+
+ expect { visit_pipelines_schedules }.not_to exceed_query_limit(control_count)
+ end
+
context 'when the scope is set to active' do
let(:scope) { 'active' }
@@ -36,20 +46,321 @@ describe Projects::PipelineSchedulesController do
end
end
- describe 'GET edit' do
- let(:user) { create(:user) }
+ describe 'GET #new' do
+ set(:user) { create(:user) }
before do
- project.add_master(user)
-
+ project.add_developer(user)
sign_in(user)
end
- it 'loads the pipeline schedule' do
- get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ it 'initializes a pipeline schedule model' do
+ get :new, namespace_id: project.namespace.to_param, project_id: project
expect(response).to have_http_status(:ok)
- expect(assigns(:schedule)).to eq(pipeline_schedule)
+ expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule)
+ end
+ end
+
+ describe 'POST #create' do
+ describe 'functionality' do
+ set(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ let(:basic_param) do
+ attributes_for(:ci_pipeline_schedule)
+ end
+
+ context 'when variables_attributes has one variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'creates a new schedule' do
+ expect { go }
+ .to change { Ci::PipelineSchedule.count }.by(1)
+ .and change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ expect(response).to have_http_status(:found)
+
+ Ci::PipelineScheduleVariable.last.tap do |v|
+ expect(v.key).to eq("AAA")
+ expect(v.value).to eq("AAA123")
+ end
+ end
+ end
+
+ context 'when variables_attributes has two variables and duplicted' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+ })
+ end
+
+ it 'returns an error that the keys of variable are duplicated' do
+ expect { go }
+ .to change { Ci::PipelineSchedule.count }.by(0)
+ .and change { Ci::PipelineScheduleVariable.count }.by(0)
+
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
+ end
+ end
+
+ describe 'security' do
+ let(:schedule) { attributes_for(:ci_pipeline_schedule) }
+
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ it { expect { go }.to be_denied_for(:visitor) }
+ end
+
+ def go
+ post :create, namespace_id: project.namespace.to_param, project_id: project, schedule: schedule
+ end
+ end
+
+ describe 'PUT #update' do
+ describe 'functionality' do
+ set(:user) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ context 'when a pipeline schedule has no variables' do
+ let(:basic_param) do
+ { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true }
+ end
+
+ context 'when params include one variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'inserts new variable to the pipeline schedule' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ pipeline_schedule.reload
+ expect(response).to have_http_status(:found)
+ expect(pipeline_schedule.variables.last.key).to eq('AAA')
+ expect(pipeline_schedule.variables.last.value).to eq('AAA123')
+ end
+ end
+
+ context 'when params include two duplicated variables' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+ })
+ end
+
+ it 'returns an error that variables are duplciated' do
+ go
+
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
+ end
+ end
+
+ context 'when a pipeline schedule has one variable' do
+ let(:basic_param) do
+ { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true }
+ end
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'CCC', pipeline_schedule: pipeline_schedule)
+ end
+
+ context 'when adds a new variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'adds the new variable' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ pipeline_schedule.reload
+ expect(pipeline_schedule.variables.last.key).to eq('AAA')
+ end
+ end
+
+ context 'when adds a new duplicated variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'CCC', value: 'AAA123' }]
+ })
+ end
+
+ it 'returns an error' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule.reload
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
+ end
+
+ context 'when updates a variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, value: 'new_value' }]
+ })
+ end
+
+ it 'updates the variable' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule_variable.reload
+ expect(pipeline_schedule_variable.value).to eq('new_value')
+ end
+ end
+
+ context 'when deletes a variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true }]
+ })
+ end
+
+ it 'delete the existsed variable' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+ end
+ end
+
+ context 'when deletes and creates a same key simultaneously' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true },
+ { key: 'CCC', value: 'CCC123' }]
+ })
+ end
+
+ it 'updates the variable' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule.reload
+ expect(pipeline_schedule.variables.last.key).to eq('CCC')
+ expect(pipeline_schedule.variables.last.value).to eq('CCC123')
+ end
+ end
+ end
+ end
+
+ describe 'security' do
+ let(:schedule) { { description: 'updated_desc' } }
+
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ it { expect { go }.to be_denied_for(:visitor) }
+
+ context 'when a developer created a pipeline schedule' do
+ let(:developer_1) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer_1) }
+
+ before do
+ project.add_developer(developer_1)
+ end
+
+ it { expect { go }.to be_allowed_for(developer_1) }
+ it { expect { go }.to be_denied_for(:developer).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ end
+
+ context 'when a master created a pipeline schedule' do
+ let(:master_1) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master_1) }
+
+ before do
+ project.add_master(master_1)
+ end
+
+ it { expect { go }.to be_allowed_for(master_1) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_denied_for(:developer).of(project) }
+ end
+ end
+
+ def go
+ put :update, namespace_id: project.namespace.to_param,
+ project_id: project, id: pipeline_schedule,
+ schedule: schedule
+ end
+ end
+
+ describe 'GET #edit' do
+ describe 'functionality' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'loads the pipeline schedule' do
+ get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+
+ expect(response).to have_http_status(:ok)
+ expect(assigns(:schedule)).to eq(pipeline_schedule)
+ end
+ end
+
+ describe 'security' do
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ it { expect { go }.to be_denied_for(:visitor) }
+ end
+
+ def go
+ get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end
+ end
+
+ describe 'GET #take_ownership' do
+ describe 'security' do
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ it { expect { go }.to be_denied_for(:visitor) }
+ end
+
+ def go
+ post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
end
end
@@ -65,7 +376,7 @@ describe Projects::PipelineSchedulesController do
end
it 'does not delete the pipeline schedule' do
- expect(response).not_to have_http_status(:ok)
+ expect(response).to have_http_status(:not_found)
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index f2b59ba82ca..8671d7a78dd 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -5,11 +5,10 @@ describe Projects::ProjectMembersController do
let(:project) { create(:empty_project, :public, :access_requestable) }
describe 'GET index' do
- it 'should have the settings/members address with a 302 status code' do
+ it 'should have the project_members address with a 200 status code' do
get :index, namespace_id: project.namespace, project_id: project
- expect(response).to have_http_status(302)
- expect(response.location).to include namespace_project_settings_members_path(project.namespace, project)
+ expect(response).to have_http_status(200)
end
end
@@ -50,7 +49,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Users were successfully added.'
- expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+ expect(response).to redirect_to(project_project_members_path(project))
end
it 'adds no user to members' do
@@ -62,7 +61,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Message'
- expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+ expect(response).to redirect_to(project_project_members_path(project))
end
end
end
@@ -111,7 +110,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(project.members).not_to include member
end
@@ -183,7 +182,7 @@ describe Projects::ProjectMembersController do
project_id: project
expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
- expect(response).to redirect_to(namespace_project_path(project.namespace, project))
+ expect(response).to redirect_to(project_path(project))
expect(project.requesters).to be_empty
expect(project.users).not_to include user
end
@@ -202,7 +201,7 @@ describe Projects::ProjectMembersController do
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(
- namespace_project_path(project.namespace, project)
+ project_path(project)
)
expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(project.users).not_to include user
@@ -253,7 +252,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(project.members).to include member
end
@@ -290,7 +289,7 @@ describe Projects::ProjectMembersController do
expect(project.team_members).to include member
expect(response).to set_flash.to 'Successfully imported'
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 4dc227a36d4..5a9d8a75f3e 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -79,7 +79,7 @@ describe Projects::ServicesController do
put :update,
namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true }
- expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(response).to redirect_to(project_settings_integrations_path(project))
expect(flash[:notice]).to eq 'HipChat activated.'
end
end
diff --git a/spec/controllers/projects/settings/members_controller_spec.rb b/spec/controllers/projects/settings/members_controller_spec.rb
deleted file mode 100644
index 076d6cd9c6e..00000000000
--- a/spec/controllers/projects/settings/members_controller_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require('spec_helper')
-
-describe Projects::Settings::MembersController do
- let(:project) { create(:empty_project, :public, :access_requestable) }
-
- describe 'GET show' do
- it 'renders show with 200 status code' do
- get :show, namespace_id: project.namespace, project_id: project
-
- expect(response).to have_http_status(200)
- expect(response).to render_template(:show)
- end
- end
-end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index ec0b7f8c967..cc444f31797 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -148,7 +148,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(Snippet.last)
+ expect(response).to redirect_to(project_snippet_path(project, Snippet.last))
end
end
end
@@ -228,7 +228,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(project_snippet_path(project, snippet))
end
end
end
@@ -273,7 +273,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(project_snippet_path(project, snippet))
end
end
end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 1ecfe48475c..da06fcb7cfb 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -15,13 +15,13 @@ describe Projects::VariablesController do
post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "one", value: "two" }
- expect(flash[:notice]).to include 'Variables were successfully updated.'
- expect(response).to redirect_to(namespace_project_settings_ci_cd_path(project.namespace, project))
+ expect(flash[:notice]).to include 'Variable was successfully created.'
+ expect(response).to redirect_to(project_settings_ci_cd_path(project))
end
end
context 'variable is invalid' do
- it 'shows an alert flash message' do
+ it 'renders show' do
post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "..one", value: "two" }
@@ -35,7 +35,6 @@ describe Projects::VariablesController do
context 'updating a variable with valid characters' do
before do
- variable.project_id = project.id
project.variables << variable
end
@@ -44,7 +43,7 @@ describe Projects::VariablesController do
id: variable.id, variable: { key: variable.key, value: 'two' }
expect(flash[:notice]).to include 'Variable was successfully updated.'
- expect(response).to redirect_to(namespace_project_variables_path(project.namespace, project))
+ expect(response).to redirect_to(project_variables_path(project))
end
it 'renders the action #show if the variable key is invalid' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 240a81367d0..f96fe7ad5cb 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -482,7 +482,7 @@ describe ProjectsController do
it 'redirects to the canonical path (testing non-show action)' do
get :refs, namespace_id: 'foo', id: 'bar'
- expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project))
+ expect(response).to redirect_to(refs_project_path(public_project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
end
end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 917bd44c91b..7340a4e16c0 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -88,7 +88,7 @@ describe SentNotificationsController, type: :controller do
it 'redirects to the issue page' do
expect(response)
- .to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+ .to redirect_to(project_issue_path(project, issue))
end
end
@@ -114,7 +114,7 @@ describe SentNotificationsController, type: :controller do
it 'redirects to the merge request page' do
expect(response)
- .to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ .to redirect_to(project_merge_request_path(project, merge_request))
end
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 430d1208cd1..15416a89017 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -341,7 +341,7 @@ describe SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(snippet_path(snippet))
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0cc498f0ce9..a77f01ecb00 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -207,7 +207,8 @@ FactoryGirl.define do
cache: {
key: 'cache_key',
untracked: false,
- paths: ['vendor/*']
+ paths: ['vendor/*'],
+ policy: 'pull-push'
}
}
end
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
new file mode 100644
index 00000000000..565ced9eb1a
--- /dev/null
+++ b/spec/factories/ci/group_variables.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :ci_group_variable, class: Ci::GroupVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ trait(:protected) do
+ protected true
+ end
+
+ group factory: :group
+ end
+end
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..ca64d1aada0
--- /dev/null
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ pipeline_schedule factory: :ci_pipeline_schedule
+ end
+end
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 6712dd5d82e..33a17cf7ed5 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do
- runner_id 1
- project_id 1
+ runner factory: :ci_runner
+ project factory: :empty_project
end
end
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 841ab3c73b8..113665ff11b 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,7 +1,13 @@
FactoryGirl.define do
factory :milestone do
title
- project factory: :empty_project
+
+ transient do
+ project nil
+ group nil
+ project_id nil
+ group_id nil
+ end
trait :active do
state "active"
@@ -11,6 +17,20 @@ FactoryGirl.define do
state "closed"
end
+ after(:build) do |milestone, evaluator|
+ if evaluator.group
+ milestone.group = evaluator.group
+ elsif evaluator.group_id
+ milestone.group_id = evaluator.group_id
+ elsif evaluator.project
+ milestone.project = evaluator.project
+ elsif evaluator.project_id
+ milestone.project_id = evaluator.project_id
+ else
+ milestone.project = create(:empty_project)
+ end
+ end
+
factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed]
end
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
deleted file mode 100644
index 0f13b2c1020..00000000000
--- a/spec/factories/personal_snippets.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-FactoryGirl.define do
- factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
- end
-end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
deleted file mode 100644
index e0fe1b36fd3..00000000000
--- a/spec/factories/project_snippets.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-FactoryGirl.define do
- factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
- project factory: :empty_project
- end
-end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index aef1c17a239..1bb2db11e7f 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -220,7 +220,7 @@ FactoryGirl.define do
active: true,
properties: {
'project_url' => 'http://redmine/projects/project_name_in_redmine',
- 'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
+ 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
}
)
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 388f662e6e5..f6ce99d50f9 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -18,4 +18,11 @@ FactoryGirl.define do
visibility_level Snippet::PRIVATE
end
end
+
+ factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
+ project factory: :empty_project
+ end
+
+ factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+ end
end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 5e6cd64c5c1..f26d3a6a72f 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -4,7 +4,7 @@ feature 'Abuse reports', feature: true do
let(:another_user) { create(:user) }
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
scenario 'Report abuse' do
@@ -12,7 +12,7 @@ feature 'Abuse reports', feature: true do
click_link 'Report abuse'
- fill_in 'abuse_report_message', with: 'This user send spam'
+ fill_in 'abuse_report_message', with: 'This user sends spam'
click_button 'Send report'
expect(page).to have_content 'Thank you for your report'
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 3a6e356b0b0..8672c009f90 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -5,7 +5,7 @@ describe "Admin::AbuseReports", feature: true, js: true do
context 'as an admin' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'if a user has been reported for abuse' do
diff --git a/spec/features/admin/admin_active_tab_spec.rb b/spec/features/admin/admin_active_tab_spec.rb
index c74336d8221..07430ecd6e0 100644
--- a/spec/features/admin/admin_active_tab_spec.rb
+++ b/spec/features/admin/admin_active_tab_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'admin active tab' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
shared_examples 'page has active tab' do |title|
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index d8fd4319328..1e2cb8569ec 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -4,7 +4,7 @@ feature 'Admin Appearance', feature: true do
let!(:appearance) { create(:appearance) }
scenario 'Create new appearance' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
fill_in 'appearance_title', with: 'MyCompany'
@@ -20,7 +20,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Preview appearance' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
click_link "Preview"
@@ -34,7 +34,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Appearance logo' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
attach_file(:appearance_logo, logo_fixture)
@@ -46,7 +46,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Header logos' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
attach_file(:appearance_header_logo, logo_fixture)
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index da063bf7b74..e55308e393b 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
feature 'Admin Broadcast Messages', feature: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
create(:broadcast_message, :expired, message: 'Migration to new server')
visit admin_broadcast_messages_path
end
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index d9c4fc686b1..31d4142a8e9 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -4,7 +4,7 @@ describe 'Admin browse spam logs' do
let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'Browse spam logs' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index c734a2ef16d..3e3404dfdac 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin browses logs' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
it 'shows available log files' do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e767081f3e5..e020579f71e 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin Builds' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'GET /admin/builds' do
diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb
index 952e5475213..6840456e509 100644
--- a/spec/features/admin/admin_cohorts_spec.rb
+++ b/spec/features/admin/admin_cohorts_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
feature 'Admin cohorts page', feature: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'See users count per month' do
diff --git a/spec/features/admin/admin_conversational_development_index_spec.rb b/spec/features/admin/admin_conversational_development_index_spec.rb
index b484677a6df..2d2c7df5364 100644
--- a/spec/features/admin/admin_conversational_development_index_spec.rb
+++ b/spec/features/admin/admin_conversational_development_index_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin Conversational Development Index' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
context 'when usage ping is disabled' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 81cddd03f80..aaeaaa829e1 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'admin deploy keys', type: :feature do
let!(:another_deploy_key) { create(:another_deploy_key, public: true) }
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
it 'show all public deploy keys' do
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 679bf63e0fd..e2280b6e3b1 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -8,7 +8,7 @@ feature 'Admin disables Git access protocol', feature: true do
background do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in(admin)
+ sign_in(admin)
end
context 'with HTTP disabled' do
@@ -51,7 +51,7 @@ feature 'Admin disables Git access protocol', feature: true do
end
def visit_project
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
def disable_http_protocol
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 5437da29979..15dc6b6c234 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
feature 'Admin disables 2FA for a user', feature: true do
scenario 'successfully', js: true do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
user = create(:user, :two_factor)
edit_user(user)
@@ -17,7 +17,7 @@ feature 'Admin disables 2FA for a user', feature: true do
end
scenario 'for a user without 2FA enabled' do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
user = create(:user)
edit_user(user)
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 8b0fafc5f07..d15d9982884 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -6,9 +6,10 @@ feature 'Admin Groups', feature: true do
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
let(:user) { create :user }
let!(:group) { create :group }
- let!(:current_user) { gitlab_sign_in :admin }
+ let!(:current_user) { create(:admin) }
before do
+ sign_in(current_user)
stub_application_setting(default_group_visibility: internal)
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 75093aa4167..c404e054dba 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -5,7 +5,7 @@ feature "Admin Health Check", feature: true do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe '#show' do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index ec80c420c79..94dace7a1fd 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -6,7 +6,7 @@ feature 'Admin::HookLogs', feature: true do
let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'show list of hook logs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index c07c21bd6a1..1e675fc0ce7 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Admin::Hooks', feature: true do
before do
@project = create(:project)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
@system_hook = create(:system_hook)
end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index bb40918bd22..ae9b47299e6 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'admin issues labels' do
let!(:feature_label) { Label.create(title: 'feature', template: true) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'list' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index ae41267e5fc..2e04a82806f 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'admin manage applications', feature: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
it do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index a4ce3e1d5ee..942cc60e5dd 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -5,8 +5,10 @@ describe "Admin::Projects", feature: true do
let(:user) { create :user }
let!(:project) { create(:project) }
- let!(:current_user) do
- gitlab_sign_in :admin
+ let!(:current_user) { create(:admin) }
+
+ before do
+ sign_in(current_user)
end
describe "GET /admin/projects" do
@@ -42,7 +44,7 @@ describe "Admin::Projects", feature: true do
end
it do
- expect(current_path).to eq admin_namespace_project_path(project.namespace, project)
+ expect(current_path).to eq admin_project_path(project)
end
it "has project info" do
@@ -62,7 +64,7 @@ describe "Admin::Projects", feature: true do
end
it 'transfers project to group web', js: true do
- visit admin_namespace_project_path(project.namespace, project)
+ visit admin_project_path(project)
click_button 'Search for Namespace'
click_link 'group: web'
@@ -79,7 +81,7 @@ describe "Admin::Projects", feature: true do
end
it 'adds admin a to a project as developer', js: true do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.users-project-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
@@ -102,7 +104,7 @@ describe "Admin::Projects", feature: true do
end
it 'removes admin from the project' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.content-list' do
expect(page).to have_content(current_user.name)
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index 2bfe401521b..bf0c21cd04a 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Admin::RequestsProfilesController', feature: true do
before do
FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
after do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 6ad2d456b93..b06e7e5037c 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -5,7 +5,7 @@ describe "Admin Runners" do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe "Runners page" do
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 2d6565e6d3b..a44fa0b86d5 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -5,7 +5,7 @@ feature 'Admin updates settings', feature: true do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_application_settings_path
end
@@ -16,6 +16,19 @@ feature 'Admin updates settings', feature: true do
expect(page).to have_content "Application settings saved successfully"
end
+ scenario 'Uncheck all restricted visibility levels' do
+ find('#application_setting_visibility_level_0').set(false)
+ find('#application_setting_visibility_level_10').set(false)
+ find('#application_setting_visibility_level_20').set(false)
+
+ click_button 'Save'
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(find('#application_setting_visibility_level_0')).not_to be_checked
+ expect(find('#application_setting_visibility_level_10')).not_to be_checked
+ expect(find('#application_setting_visibility_level_20')).not_to be_checked
+ end
+
scenario 'Change application settings' do
uncheck 'Gravatar enabled'
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 4efc7f0eb48..1fd1cda694a 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin System Info' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'GET /admin/system_info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 231c094c91d..cd7040891e9 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -13,7 +13,7 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
end
before do
- gitlab_sign_in(admin)
+ sign_in(admin)
end
describe "token creation" do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 6dbc697642f..3bc8f8aed54 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -5,7 +5,11 @@ describe "Admin::Users", feature: true do
create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
end
- let!(:current_user) { gitlab_sign_in :admin }
+ let!(:current_user) { create(:admin) }
+
+ before do
+ sign_in(current_user)
+ end
describe "GET /admin/users" do
before do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 91d70435db8..113353862be 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -5,7 +5,7 @@ feature 'Admin uses repository checks', feature: true do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'to trigger a single check' do
@@ -43,6 +43,6 @@ feature 'Admin uses repository checks', feature: true do
end
def visit_admin_project_page(project)
- visit admin_namespace_project_path(project.namespace, project)
+ visit admin_project_path(project)
end
end
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index b94ad973fed..011fdce21d8 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -15,8 +15,8 @@ describe 'Issues Feed', feature: true do
context 'when authenticated' do
it 'renders atom feed' do
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, :atom)
+ sign_in user
+ visit project_issues_path(project, :atom)
expect(response_headers['Content-Type'])
.to have_content('application/atom+xml')
@@ -30,7 +30,7 @@ describe 'Issues Feed', feature: true do
context 'when authenticated via private token' do
it 'renders atom feed' do
- visit namespace_project_issues_path(project.namespace, project, :atom,
+ visit project_issues_path(project, :atom,
private_token: user.private_token)
expect(response_headers['Content-Type'])
@@ -45,7 +45,7 @@ describe 'Issues Feed', feature: true do
context 'when authenticated via RSS token' do
it 'renders atom feed' do
- visit namespace_project_issues_path(project.namespace, project, :atom,
+ visit project_issues_path(project, :atom,
rss_token: user.rss_token)
expect(response_headers['Content-Type'])
@@ -59,7 +59,7 @@ describe 'Issues Feed', feature: true do
end
it "renders atom feed with url parameters for project issues" do
- visit namespace_project_issues_path(project.namespace, project,
+ visit project_issues_path(project,
:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 74f5f70702a..dff6f96b663 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -7,7 +7,7 @@ describe 'Auto deploy' do
before do
create :kubernetes_service, project: project
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'when no deployment service is active' do
@@ -16,7 +16,7 @@ describe 'Auto deploy' do
end
it 'does not show a button to set up auto deploy' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_no_content('Set up auto deploy')
end
end
@@ -24,7 +24,7 @@ describe 'Auto deploy' do
context 'when a deployment service is active' do
before do
project.kubernetes_service.update!(active: true)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows a button to set up auto deploy' do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index ba58af22841..d883b467c67 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -14,14 +14,14 @@ describe 'Issue Boards add issue modal', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
it 'resets filtered search state' do
- visit namespace_project_board_path(project.namespace, project, board, search: 'testing')
+ visit project_board_path(project, board, search: 'testing')
wait_for_requests
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 87fc31d414c..3d7e26c7e19 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -12,12 +12,12 @@ describe 'Issue Boards', feature: true, js: true do
project.team << [user, :master]
project.team << [user2, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'no lists' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
end
@@ -81,7 +81,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
@@ -158,7 +158,7 @@ describe 'Issue Boards', feature: true, js: true do
create(:labeled_issue, project: project, labels: [planning])
end
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
page.within(find('.board:nth-child(2)')) do
@@ -507,7 +507,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'keyboard shortcuts' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -519,8 +519,8 @@ describe 'Issue Boards', feature: true, js: true do
context 'signed out user' do
before do
- gitlab_sign_out
- visit namespace_project_board_path(project.namespace, project, board)
+ sign_out(:user)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -542,9 +542,9 @@ describe 'Issue Boards', feature: true, js: true do
before do
project.team << [user_guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(user_guest)
- visit namespace_project_board_path(project.namespace, project, board)
+ sign_out(:user)
+ sign_in(user_guest)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 1e620061e5e..17b0da80947 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -15,14 +15,14 @@ describe 'Issue Boards', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'un-ordered issues' do
let!(:issue4) { create(:labeled_issue, project: project, labels: [label]) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -47,7 +47,7 @@ describe 'Issue Boards', :feature, :js do
context 'ordering in list' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -110,7 +110,7 @@ describe 'Issue Boards', :feature, :js do
let!(:issue6) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label2], relative_position: 1.0) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 4)
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index ed3b38e6a7e..8c16148023e 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -6,9 +6,9 @@ describe 'Issue Boards shortcut', feature: true, js: true do
before do
create(:board, project: project)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'takes user to issue board index' do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 8899e1ef5e5..ce05bb71759 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -12,7 +12,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows empty state when no results found' do
@@ -202,7 +202,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
end
def visit_board
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
click_button('Add issues')
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index 77cd87d6601..6b267694201 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -10,9 +10,9 @@ describe 'Issue Boards new issue', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -83,7 +83,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
context 'unauthorized user' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 301c243febd..fa17ef92bbb 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -20,9 +20,9 @@ describe 'Issue Boards', feature: true, js: true do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -79,6 +79,22 @@ describe 'Issue Boards', feature: true, js: true do
end
end
+ it 'does not show remove button for backlog or closed issues' do
+ create(:issue, project: project)
+ create(:issue, :closed, project: project)
+
+ visit project_board_path(project, board)
+ wait_for_requests
+
+ click_card(find('.board:nth-child(1)').first('.card'))
+
+ expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+
+ click_card(find('.board:nth-child(3)').first('.card'))
+
+ expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+ end
+
context 'assignee' do
it 'updates the issues assignee' do
click_card(card)
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index d57ae6a71e7..f88bf237301 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -13,9 +13,9 @@ describe 'Sub-group project issue boards', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index b2e72fc7dee..adbd82e3057 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -68,7 +68,7 @@ feature 'Contributions Calendar', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
end
describe 'calendar day selection' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index de16ee3e567..af4cc00162a 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'CI Lint', js: true do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
describe 'YAML parsing' do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 0373f649ee8..fb1e47994ef 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -66,7 +66,7 @@ describe 'Commits' do
end
before do
- visit namespace_project_commits_path(project.namespace, project, :master)
+ visit project_commits_path(project, :master)
end
it 'shows correct build status from default branch' do
@@ -192,7 +192,7 @@ describe 'Commits' do
before do
project.team << [user, :master]
sign_in(user)
- visit namespace_project_commits_path(project.namespace, project, branch_name)
+ visit project_commits_path(project, branch_name)
end
it 'includes the committed_date for each commit' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 80d16539d5a..8f59ce3d2e7 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -9,7 +9,7 @@ describe "Container Registry" do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_developer(user)
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: [])
@@ -55,7 +55,6 @@ describe "Container Registry" do
end
def visit_container_registry
- visit namespace_project_container_registry_index_path(
- project.namespace, project)
+ visit project_container_registry_index_path(project)
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 005c88f6bab..11d5a4f421f 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -6,7 +6,7 @@ describe 'Copy as GFM', feature: true, js: true do
include ActionView::Helpers::JavaScriptHelper
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'Copying rendered GFM' do
@@ -16,7 +16,7 @@ describe 'Copy as GFM', feature: true, js: true do
# `markdown` helper expects a `@project` variable
@project = @feat.project
- visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
+ visit project_issue_path(@project, @feat.issue)
end
# The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
@@ -121,13 +121,13 @@ describe 'Copy as GFM', feature: true, js: true do
# full issue reference
@feat.issue.to_reference(full: true),
# issue URL
- namespace_project_issue_url(@project.namespace, @project, @feat.issue),
+ project_issue_url(@project, @feat.issue),
# issue URL with note anchor
- namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'),
+ project_issue_url(@project, @feat.issue, anchor: 'note_123'),
# issue link
- "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
+ "[Issue](#{project_issue_url(@project, @feat.issue)})",
# issue link with note anchor
- "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})"
+ "[Issue](#{project_issue_url(@project, @feat.issue, anchor: 'note_123')})"
)
verify(
@@ -466,7 +466,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a diff' do
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
context 'selecting one word of text' do
@@ -507,7 +507,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a blob' do
before do
- visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
+ visit project_blob_path(project, File.join('master', 'files/ruby/popen.rb'))
wait_for_requests
end
@@ -549,7 +549,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a GFM code block' do
before do
- visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
+ visit project_blob_path(project, File.join('markdown', 'doc/api/users.md'))
wait_for_requests
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 5a7ea975455..f530063352a 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -14,9 +14,9 @@ feature 'Cycle Analytics', feature: true, js: true do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
@@ -38,8 +38,8 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(user)
+ visit project_cycle_analytics_path(project)
end
it 'shows data on each stage' do
@@ -70,8 +70,8 @@ feature 'Cycle Analytics', feature: true, js: true do
user.update_attribute(:preferred_language, 'es')
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(user)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
@@ -93,8 +93,8 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(guest)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(guest)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index f7ddded10c1..203d206b80b 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'Dashboard Active Tab', js: true, feature: true do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
shared_examples 'page has active tab' do |title|
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index 1e9cabe7850..ebfe7340eb7 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
RSpec.describe 'Dashboard Activity', feature: true do
+ let(:user) { create(:user) }
+
before do
- gitlab_sign_in(create :user)
+ sign_in(user)
visit activity_dashboard_path
end
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index a5ba3e7e3cf..dda4d517e39 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'Dashboard Archived Project', feature: true do
project.team << [user, :master]
archived_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_projects_path
end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 6931d0a840e..8949267c82e 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -13,7 +13,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
Event.create( project: project, author_id: user.id, action: Event::JOINED,
updated_at: created_date, created_at: created_date)
- gitlab_sign_in user
+ sign_in user
visit user_path(user)
wait_for_requests()
@@ -30,7 +30,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
project.team << [user, :master]
create(:snippet, author: user, updated_at: created_date, created_at: created_date)
- gitlab_sign_in user
+ sign_in user
visit user_snippets_path(user)
wait_for_requests()
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 2f7245950ec..ffaefb9c632 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'Dashboard Group', feature: true do
before do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
end
it 'creates new group', js: true do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index e520027bc38..54a01e837de 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -10,7 +10,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
nested_group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
expect(page).to have_content(group.full_name)
@@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
nested_group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
@@ -58,7 +58,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
subgroup.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
@@ -98,7 +98,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 25b0f40c9cd..fa7ea4c96b6 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'Dashboard Help', feature: true do
before do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
end
it 'renders correctly markdown' do
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 8a8a20fd5b1..285724f4b48 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -9,7 +9,7 @@ describe 'Navigation bar counter', feature: true, caching: true do
before do
issue.assignees = [user]
merge_request.update(assignee: user)
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'reflects dashboard issues count' do
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index a57962abbda..86ac24ea06e 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
RSpec.describe 'Dashboard Issues', feature: true do
let(:current_user) { create :user }
+ let(:user) { current_user } # Shared examples depend on this being available
let!(:public_project) { create(:empty_project, :public) }
let(:project) { create(:empty_project) }
let(:project_with_issues_disabled) { create(:empty_project, :issues_disabled) }
@@ -12,7 +13,7 @@ RSpec.describe 'Dashboard Issues', feature: true do
before do
[project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] }
- gitlab_sign_in(current_user)
+ sign_in(current_user)
visit issues_dashboard_path(assignee_id: current_user.id)
end
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 88bbb9e75b9..8b7dacef913 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -11,7 +11,7 @@ describe 'Dashboard > label filter', feature: true, js: true do
project.labels << label
project2.labels << label2
- gitlab_sign_in(user)
+ sign_in(user)
visit issues_dashboard_path
end
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index b0e4036f27c..d06497041de 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -11,7 +11,7 @@ feature 'Dashboard > milestone filter', :feature, :js do
let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit issues_dashboard_path(author_id: user.id)
end
diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb
index cc4193b180f..8340a4f59df 100644
--- a/spec/features/dashboard/milestone_tabs_spec.rb
+++ b/spec/features/dashboard/milestone_tabs_spec.rb
@@ -15,7 +15,7 @@ describe 'Dashboard milestone tabs', :js, :feature do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 0ba87d921d0..ea0b2e99c3e 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -10,7 +10,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
wait_for_requests
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index f29186f368d..7ca002fc821 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,13 +1,19 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Projects', feature: true do
+feature 'Dashboard Projects' do
let(:user) { create(:user) }
- let(:project) { create(:project, name: "awesome stuff") }
+ let(:project) { create(:project, name: 'awesome stuff') }
let(:project2) { create(:project, :public, name: 'Community project') }
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
+ end
+
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do
+ before do
+ visit dashboard_projects_path
+ end
end
it 'shows the project the user in a member of in the list' do
@@ -15,6 +21,14 @@ RSpec.describe 'Dashboard Projects', feature: true do
expect(page).to have_content('awesome stuff')
end
+ it 'shows "New project" button' do
+ visit dashboard_projects_path
+
+ page.within '#content-body' do
+ expect(page).to have_link('New project')
+ end
+ end
+
context 'when last_repository_updated_at, last_activity_at and update_at are present' do
it 'shows the last_repository_updated_at attribute as the update date' do
project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago)
@@ -47,8 +61,8 @@ RSpec.describe 'Dashboard Projects', feature: true do
end
end
- describe "with a pipeline", redis: true do
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
+ describe 'with a pipeline', redis: true do
+ let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
before do
# Since the cache isn't updated when a new pipeline is created
@@ -60,9 +74,7 @@ RSpec.describe 'Dashboard Projects', feature: true do
it 'shows that the last pipeline passed' do
visit dashboard_projects_path
- expect(page).to have_xpath("//a[@href='#{pipelines_namespace_project_commit_path(project.namespace, project, project.commit)}']")
+ expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
end
end
-
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 525b0e1b210..bb29dae1bdc 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Dashboard shortcuts', :feature, :js do
context 'logged in' do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
visit root_dashboard_path
end
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index 0c069ae5cf0..c5ae9aad9c6 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -6,7 +6,7 @@ describe 'Dashboard snippets', feature: true do
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(project.owner)
+ sign_in(project.owner)
visit dashboard_snippets_path
end
@@ -25,7 +25,7 @@ describe 'Dashboard snippets', feature: true do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_snippets_path
end
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index 5858f4aa101..d49a78b290f 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -83,7 +83,7 @@ feature 'Dashboard > User sorts todos' do
create(:todo, user: user, project: project, target: issue_2)
create(:todo, user: user, project: project, target: merge_request_1)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_todos_path
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 24da5db305f..30bab7eeaa7 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -317,23 +317,6 @@ feature 'Dashboard Todos' do
end
end
- context 'User has a Todo in a project pending deletion' do
- before do
- deleted_project = create(:project, :public, pending_delete: true)
- create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author)
- create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done)
- sign_in(user)
- visit dashboard_todos_path
- end
-
- it 'shows "All done" message' do
- within('.todos-count') { expect(page).to have_content '0' }
- expect(page).to have_content 'To do 0'
- expect(page).to have_content 'Done 0'
- expect(page).to have_selector('.todos-all-done', count: 1)
- end
- end
-
context 'User has a Build Failed todo' do
let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
@@ -347,7 +330,7 @@ feature 'Dashboard Todos' do
end
it 'links to the pipelines for the merge request' do
- href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
+ href = pipelines_project_merge_request_path(project, todo.target)
expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index e9f34760143..711d3617335 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -9,7 +9,7 @@ describe 'Dashboard > User filters projects', :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'filtering personal projects' do
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index c4dbaad2895..f235fef1aa4 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -8,7 +8,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
context 'filtering by milestone' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project, author: user, assignees: [user])
create(:issue, project: project, author: user, assignees: [user], milestone: milestone)
diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard_milestones_spec.rb
index b308a2297b9..7a6a448d4c2 100644
--- a/spec/features/dashboard_milestones_spec.rb
+++ b/spec/features/dashboard_milestones_spec.rb
@@ -17,7 +17,7 @@ feature 'Dashboard > Milestones', feature: true do
let!(:milestone) { create(:milestone, project: project) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_milestones_path
end
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 96128061e4d..26d21207678 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -9,9 +9,9 @@ describe 'Discussion Comments Merge Request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'discussion comments', 'commit'
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index d7c1cd12fb5..11dbe10e1df 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like 'discussion comments', 'issue'
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index 31fb9c72d25..db745be6fa1 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Merge Request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'discussion comments', 'merge request'
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index 998d633c83d..eddbd4bde9b 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
end
it_behaves_like 'discussion comments', 'snippet'
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index ea749528c11..18c06a48111 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -10,11 +10,11 @@ feature 'Expand and collapse diffs', js: true, feature: true do
allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
# Ensure that undiffable.md is in .gitattributes
project.repository.copy_gitattributes(branch)
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+ visit project_commit_path(project, project.commit(branch))
execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
@@ -38,7 +38,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
+ visit project_commit_path(project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
execute_script('window.location.reload()')
wait_for_requests
@@ -52,7 +52,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
+ visit project_commit_path(project, project.commit(branch), anchor: large_diff[:id])
execute_script('window.location.reload()')
wait_for_requests
@@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
before do
large_diff.find('.diff-line-num', match: :prefer_exact).hover
- large_diff.find('.add-diff-note').click
+ large_diff.find('.add-diff-note', match: :prefer_exact).click
large_diff.find('.note-textarea').send_keys comment_text
large_diff.find_button('Comment').click
wait_for_requests
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 6be5dee0c3c..008d12714cc 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -10,7 +10,7 @@ describe 'Explore Groups page', :js, :feature do
before do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit explore_groups_path
end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 2d7e703688f..7dd69f550ac 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -16,7 +16,7 @@ feature 'Top Plus Menu', feature: true, js: true do
context 'used by full user' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'click on New project shows new project page' do
@@ -47,7 +47,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New issue shows new issue page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New issue")
@@ -56,7 +56,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New merge request shows new merge request page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New merge request")
@@ -66,7 +66,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New project snippet shows new snippet page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.header-content' do
find('.header-new-dropdown-toggle').trigger('click')
@@ -103,11 +103,11 @@ feature 'Top Plus Menu', feature: true, js: true do
context 'used by guest user' do
before do
- gitlab_sign_in(guest_user)
+ sign_in(guest_user)
end
scenario 'click on New issue shows new issue page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New issue")
@@ -116,31 +116,31 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'has no New merge request menu item' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
hasnot_topmenuitem("New merge request")
end
scenario 'has no New project snippet menu item' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
end
scenario 'public project has no New Issue Button' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
hasnot_topmenuitem("New issue")
end
scenario 'public project has no New merge request menu item' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
hasnot_topmenuitem("New merge request")
end
scenario 'public project has no New project snippet menu item' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 2d13af2a52a..8659a868682 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -25,25 +25,25 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in commits#index" do
- visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
+ visit project_commits_path(project, 'master', limit: 1)
expect(page).to have_link(issue.to_reference)
end
it "renders title in commits#show" do
- visit namespace_project_commit_path(project.namespace, project, commit)
+ visit project_commit_path(project, commit)
expect(page).to have_link(issue.to_reference)
end
it "renders description in commits#show" do
- visit namespace_project_commit_path(project.namespace, project, commit)
+ visit project_commit_path(project, commit)
expect(page).to have_link(fred.to_reference)
end
it "renders title in repositories#branches" do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
expect(page).to have_link(issue.to_reference)
end
@@ -66,19 +66,19 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders subject in issues#index" do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_link(@other_issue.to_reference)
end
it "renders subject in issues#show" do
- visit namespace_project_issue_path(project.namespace, project, @issue)
+ visit project_issue_path(project, @issue)
expect(page).to have_link(@other_issue.to_reference)
end
it "renders details in issues#show" do
- visit namespace_project_issue_path(project.namespace, project, @issue)
+ visit project_issue_path(project, @issue)
expect(page).to have_link(fred.to_reference)
end
@@ -92,13 +92,13 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in merge_requests#index" do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_link(issue.to_reference)
end
it "renders title in merge_requests#show" do
- visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
+ visit project_merge_request_path(project, @merge_request)
expect(page).to have_link(issue.to_reference)
end
@@ -113,19 +113,19 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in milestones#index" do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
expect(page).to have_link(issue.to_reference)
end
it "renders title in milestones#show" do
- visit namespace_project_milestone_path(project.namespace, project, @milestone)
+ visit project_milestone_path(project, @milestone)
expect(page).to have_link(issue.to_reference)
end
it "renders description in milestones#show" do
- visit namespace_project_milestone_path(project.namespace, project, @milestone)
+ visit project_milestone_path(project, @milestone)
expect(page).to have_link(fred.to_reference)
end
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 54ebfe6cf77..efa5e95de89 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -6,7 +6,7 @@ feature 'Global search', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'I search through the issues and I see pagination' do
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
new file mode 100644
index 00000000000..37814ba6238
--- /dev/null
+++ b/spec/features/group_variables_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+feature 'Group variables', js: true do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ background do
+ group.add_master(user)
+ gitlab_sign_in(user)
+ end
+
+ context 'when user creates a new variable' do
+ background do
+ visit group_settings_ci_cd_path(group)
+ fill_in 'variable_key', with: 'AAA'
+ fill_in 'variable_value', with: 'AAA123'
+ find(:css, "#variable_protected").set(true)
+ click_on 'Add new variable'
+ end
+
+ scenario 'user sees the created variable' do
+ page.within('.variables-table') do
+ expect(find(".variable-key")).to have_content('AAA')
+ expect(find(".variable-value")).to have_content('******')
+ expect(find(".variable-protected")).to have_content('Yes')
+ end
+ click_on 'Reveal Values'
+ page.within('.variables-table') do
+ expect(find(".variable-value")).to have_content('AAA123')
+ end
+ end
+ end
+
+ context 'when user edits a variable' do
+ background do
+ create(:ci_group_variable, key: 'AAA', value: 'AAA123', protected: true,
+ group: group)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within('.variable-menu') do
+ click_on 'Update'
+ end
+
+ fill_in 'variable_key', with: 'BBB'
+ fill_in 'variable_value', with: 'BBB123'
+ find(:css, "#variable_protected").set(false)
+ click_on 'Save variable'
+ end
+
+ scenario 'user sees the updated variable' do
+ page.within('.variables-table') do
+ expect(find(".variable-key")).to have_content('BBB')
+ expect(find(".variable-value")).to have_content('******')
+ expect(find(".variable-protected")).to have_content('No')
+ end
+ end
+ end
+
+ context 'when user deletes a variable' do
+ background do
+ create(:ci_group_variable, key: 'BBB', value: 'BBB123', protected: false,
+ group: group)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within('.variable-menu') do
+ page.accept_alert 'Are you sure?' do
+ click_on 'Remove'
+ end
+ end
+ end
+
+ scenario 'user does not see the deleted variable' do
+ expect(page).to have_no_css('.variables-table')
+ end
+ end
+end
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 9f66a3d8c72..262d9434ddf 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
feature 'Group activity page', feature: true do
+ let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
let(:group) { create(:group) }
let(:path) { activity_group_path(group) }
context 'when signed in' do
before do
- user = create(:group_member, :developer, user: create(:user), group: group ).user
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index b1c7151dfa8..e2c7907528b 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -5,7 +5,7 @@ feature 'Groups Merge Requests Empty States' do
let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'group has a project' do
diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb
index f450626c370..ea779a3baf0 100644
--- a/spec/features/groups/group_name_toggle_spec.rb
+++ b/spec/features/groups/group_name_toggle_spec.rb
@@ -9,7 +9,7 @@ feature 'Group name toggle', feature: true, js: true do
SMALL_SCREEN = 300
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
it 'is not present if enough horizontal space' do
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 56e163ec4d0..f7ef7f29066 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -6,7 +6,7 @@ feature 'Edit group settings', feature: true do
background do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'when the group path is changed' do
diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb
index b33040ef843..88d104d5a06 100644
--- a/spec/features/groups/labels/edit_spec.rb
+++ b/spec/features/groups/labels/edit_spec.rb
@@ -7,7 +7,7 @@ feature 'Edit group label', feature: true do
background do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_group_label_path(group, label)
end
diff --git a/spec/features/groups/members/manage_access_requests_spec.rb b/spec/features/groups/members/manage_access_requests_spec.rb
index f84d8594c65..51a4d769b9c 100644
--- a/spec/features/groups/members/manage_access_requests_spec.rb
+++ b/spec/features/groups/members/manage_access_requests_spec.rb
@@ -8,7 +8,7 @@ feature 'Groups > Members > Manage access requests', feature: true do
background do
group.request_access(user)
group.add_owner(owner)
- gitlab_sign_in(owner)
+ sign_in(owner)
end
scenario 'owner can see access requests' do
diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb
index a9a654b20e2..4b226893701 100644
--- a/spec/features/groups/members/manage_members.rb
+++ b/spec/features/groups/members/manage_members.rb
@@ -8,7 +8,7 @@ feature 'Groups > Members > Manage members', feature: true do
let(:group) { create(:group) }
background do
- gitlab_sign_in(user1)
+ sign_in(user1)
end
scenario 'update user to owner level', :js do
diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb
index 41c31b62e18..3764e4792ca 100644
--- a/spec/features/groups/members/request_access_spec.rb
+++ b/spec/features/groups/members/request_access_spec.rb
@@ -8,7 +8,7 @@ feature 'Groups > Members > Request access', feature: true do
background do
group.add_owner(owner)
- gitlab_sign_in(user)
+ sign_in(user)
visit group_path(group)
end
diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb
index 8ee61953844..169827f5d0d 100644
--- a/spec/features/groups/members/sort_members_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -9,7 +9,7 @@ feature 'Groups > Members > Sort members', feature: true do
create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago)
create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago)
- gitlab_sign_in(owner)
+ sign_in(owner)
end
scenario 'sorts alphabetically by default' do
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 330310eae6b..0f3f005040f 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -8,7 +8,7 @@ feature 'Group milestones', :feature, :js do
before do
Timecop.freeze
- gitlab_sign_in(user)
+ sign_in(user)
end
after do
@@ -33,4 +33,32 @@ feature 'Group milestones', :feature, :js do
expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y'))
end
end
+
+ context 'milestones list' do
+ let!(:other_project) { create(:project_empty_repo, group: group) }
+
+ let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
+ let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') }
+ let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
+ let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
+ let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
+
+ before do
+ visit group_milestones_path(group)
+ end
+
+ it 'counts milestones correctly' do
+ expect(find('.top-area .active .badge').text).to eq("2")
+ expect(find('.top-area .closed .badge').text).to eq("2")
+ expect(find('.top-area .all .badge').text).to eq("4")
+ end
+
+ it 'lists legacy group milestones and group milestones' do
+ legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
+
+ expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
+ expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
+ end
+ end
end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 76575f61528..cbf97d0674b 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -5,9 +5,12 @@ feature 'Group show page', feature: true do
let(:path) { group_path(group) }
context 'when signed in' do
+ let(:user) do
+ create(:group_member, :developer, user: create(:user), group: group ).user
+ end
+
before do
- user = create(:group_member, :developer, user: create(:user), group: group ).user
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index ecacca00a61..6f8c8999f98 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
feature 'Group', feature: true do
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
matcher :have_namespace_error_message do
@@ -108,8 +108,8 @@ feature 'Group', feature: true do
before do
group.add_owner(user)
- gitlab_sign_out
- gitlab_sign_in(user)
+ sign_out(:user)
+ sign_in(user)
visit subgroups_group_path(group)
click_link 'New Subgroup'
@@ -128,14 +128,14 @@ feature 'Group', feature: true do
it 'checks permissions to avoid exposing groups by parent_id' do
group = create(:group, :private, path: 'secret-group')
- gitlab_sign_out
- gitlab_sign_in(:user)
+ sign_out(:user)
+ sign_in(create(:user))
visit new_group_path(parent_id: group.id)
expect(page).not_to have_content('secret-group')
end
- describe 'group edit' do
+ describe 'group edit', js: true do
let(:group) { create(:group) }
let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' }
@@ -157,8 +157,8 @@ feature 'Group', feature: true do
end
it 'removes group' do
- click_link 'Remove group'
-
+ expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
+ expect(group.members.all.count).to be_zero
expect(page).to have_content "scheduled for deletion"
end
end
@@ -212,4 +212,10 @@ feature 'Group', feature: true do
expect(page).to have_content(nested_group.name)
end
end
+
+ def remove_with_confirm(button_text, confirm_with)
+ click_button button_text
+ fill_in 'confirm_name_input', with: confirm_with
+ click_button 'Confirm'
+ end
end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index b01ee1cf491..7fe65ee554d 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -40,7 +40,7 @@ describe 'Help Pages', feature: true do
allow_any_instance_of(ApplicationSetting).to receive(:version_check_enabled) { true }
allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' }
- gitlab_sign_in :user
+ sign_in(create(:user))
visit help_path
end
@@ -60,7 +60,7 @@ describe 'Help Pages', feature: true do
allow_any_instance_of(ApplicationSetting).to receive(:help_page_text) { "My Custom Text" }
allow_any_instance_of(ApplicationSetting).to receive(:help_page_support_url) { "http://example.com/help" }
- gitlab_sign_in(:user)
+ sign_in(create(:user))
visit help_path
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
new file mode 100644
index 00000000000..9a99bb705b7
--- /dev/null
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+
+describe 'Issuables Close/Reopen/Report toggle', :feature do
+ let(:user) { create(:user) }
+
+ shared_examples 'an issuable close/reopen/report toggle' do
+ let(:container) { find('.issuable-close-dropdown') }
+ let(:human_model_name) { issuable.model_name.human.downcase }
+
+ it 'shows toggle' do
+ expect(page).to have_link("Close #{human_model_name}")
+ expect(page).to have_selector('.issuable-close-dropdown')
+ end
+
+ it 'opens a dropdown when toggle is clicked' do
+ container.find('.dropdown-toggle').click
+
+ expect(container).to have_selector('.dropdown-menu')
+ expect(container).to have_content("Close #{human_model_name}")
+ expect(container).to have_content('Report abuse')
+ expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.")
+ expect(container).to have_selector('.close-item.droplab-item-selected')
+ expect(container).to have_selector('.report-item')
+ expect(container).not_to have_selector('.report-item.droplab-item-selected')
+ expect(container).not_to have_selector('.reopen-item')
+ end
+
+ it 'changes the button when an item is selected' do
+ button = container.find('.issuable-close-button')
+
+ container.find('.dropdown-toggle').click
+ container.find('.report-item').click
+
+ expect(container).not_to have_selector('.dropdown-menu')
+ expect(button).to have_content('Report abuse')
+
+ container.find('.dropdown-toggle').click
+ container.find('.close-item').click
+
+ expect(button).to have_content("Close #{human_model_name}")
+ end
+ end
+
+ context 'on an issue' do
+ let(:project) { create(:empty_project) }
+ let(:issuable) { create(:issue, project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ context 'when user has permission to update', :js do
+ before do
+ visit project_issue_path(project, issuable)
+ end
+
+ it_behaves_like 'an issuable close/reopen/report toggle'
+ end
+
+ context 'when user doesnt have permission to update' do
+ let(:cant_project) { create(:empty_project) }
+ let(:cant_issuable) { create(:issue, project: cant_project) }
+
+ before do
+ cant_project.add_guest(user)
+
+ visit project_issue_path(cant_project, cant_issuable)
+ end
+
+ it 'only shows the `Report abuse` and `New issue` buttons' do
+ expect(page).to have_link('Report abuse')
+ expect(page).to have_link('New issue')
+ expect(page).not_to have_link('Close issue')
+ expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_link('Edit')
+ end
+ end
+ end
+
+ context 'on a merge request' do
+ let(:project) { create(:project) }
+ let(:issuable) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ context 'when user has permission to update', :js do
+ before do
+ visit project_merge_request_path(project, issuable)
+ end
+
+ it_behaves_like 'an issuable close/reopen/report toggle'
+ end
+
+ context 'when user doesnt have permission to update' do
+ let(:cant_project) { create(:project) }
+ let(:cant_issuable) { create(:merge_request, source_project: cant_project) }
+
+ before do
+ cant_project.add_reporter(user)
+
+ visit project_merge_request_path(cant_project, cant_issuable)
+ end
+
+ it 'only shows a `Report abuse` button' do
+ expect(page).to have_link('Report abuse')
+ expect(page).not_to have_link('Close merge request')
+ expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_link('Edit')
+ end
+ end
+ end
+end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index f3a5a8463d1..32fee2d9c34 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -8,7 +8,7 @@ describe 'issuable list', feature: true do
before do
project.add_user(user, :developer)
- gitlab_sign_in(user)
+ sign_in(user)
issuable_types.each { |type| create_issuables(type) }
end
@@ -39,9 +39,9 @@ describe 'issuable list', feature: true do
def visit_issuable_list(issuable_type)
if issuable_type == :issue
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
else
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
end
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
new file mode 100644
index 00000000000..948d151a517
--- /dev/null
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe 'Issue Sidebar on Mobile' do
+ include MobileHelpers
+
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:issue) { create(:issue, project: project) }
+ let!(:user) { create(:user)}
+
+ before do
+ sign_in(user)
+ end
+
+ context 'mobile sidebar on merge requests', js: true do
+ before do
+ visit project_merge_request_path(merge_request.project, merge_request)
+ end
+
+ it_behaves_like "issue sidebar stays collapsed on mobile"
+ end
+
+ context 'mobile sidebar on issues', js: true do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it_behaves_like "issue sidebar stays collapsed on mobile"
+ end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 6698e2c79a1..823c779e0d9 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -12,14 +12,14 @@ describe 'Awards Emoji', feature: true do
context 'authorized user' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
before do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -33,7 +33,7 @@ describe 'Awards Emoji', feature: true do
let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -97,7 +97,7 @@ describe 'Awards Emoji', feature: true do
context 'unauthorized user', js: true do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'has disabled emoji button' do
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index a1c97caea20..76cffc1d8c9 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -7,8 +7,8 @@ feature 'Issue awards', js: true, feature: true do
describe 'logged in' do
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -17,7 +17,7 @@ feature 'Issue awards', js: true, feature: true do
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(first('.js-emoji-btn')).to have_content '1'
end
@@ -26,7 +26,7 @@ feature 'Issue awards', js: true, feature: true do
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(first('.js-emoji-btn')).to have_content '0'
end
@@ -40,7 +40,7 @@ feature 'Issue awards', js: true, feature: true do
describe 'logged out' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index a99c19cb787..034d8afb54d 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -13,7 +13,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'sidebar' do
@@ -346,9 +346,9 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'as a guest' do
before do
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
context 'cannot bulk assign labels' do
@@ -410,7 +410,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
def enable_bulk_update
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index aa538803dd8..6e778f4d7e5 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -8,11 +8,11 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
context 'for team members' do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'allows creating a merge request from the issue page' do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
select_dropdown_option('create-mr')
@@ -21,21 +21,21 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
expect(page).to have_content("created branch 1-cherry-coloured-funk")
expect(page).to have_content("mentioned in merge request !1")
- visit namespace_project_merge_request_path(project.namespace, project, MergeRequest.first)
+ visit project_merge_request_path(project, MergeRequest.first)
expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
- expect(current_path).to eq(namespace_project_merge_request_path(project.namespace, project, MergeRequest.first))
+ expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
end
it 'allows creating a branch from the issue page' do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
select_dropdown_option('create-branch')
wait_for_requests
expect(page).to have_selector('.dropdown-toggle-text ', text: '1-cherry-coloured-funk')
- expect(current_path).to eq namespace_project_tree_path(project.namespace, project, '1-cherry-coloured-funk')
+ expect(current_path).to eq project_tree_path(project, '1-cherry-coloured-funk')
end
context "when there is a referenced merge request" do
@@ -52,7 +52,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
before do
referenced_mr.cache_merge_request_closes_issues!(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'disables the create branch button' do
@@ -66,7 +66,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
it 'disables the create branch button' do
issue = create(:issue, :confidential, project: project)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).not_to have_css('.create-mr-dropdown-wrap')
end
@@ -75,7 +75,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
context 'for visitors' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'shows no buttons' do
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index 5f631043e15..dd9a7f1253d 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -9,13 +9,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
describe 'as a user with access to the project' do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in user
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a button to resolve all discussions by creating a new issue' do
within('#resolve-count-app') do
- expect(page).to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
@@ -25,13 +25,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
end
it 'hides the link for creating a new issue' do
- expect(page).not_to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).not_to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
context 'creating an issue for discussions' do
before do
- click_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ click_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it_behaves_like 'creating an issue for a discussion'
@@ -45,7 +45,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
context 'with the internal tracker disabled' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to create a new issue' do
@@ -55,7 +55,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
context 'merge request has discussions that need to be resolved' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a warning that the merge request contains unresolved discussions' do
@@ -64,13 +64,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
it 'has a link to resolve all discussions by creating an issue' do
page.within '.mr-widget-body' do
- expect(page).to have_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).to have_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
context 'creating an issue for discussions' do
before do
- page.click_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ page.click_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it_behaves_like 'creating an issue for a discussion'
@@ -82,8 +82,8 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
describe 'as a reporter' do
before do
project.team << [user, :reporter]
- gitlab_sign_in user
- visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ sign_in user
+ visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it 'Shows a notice to ask someone else to resolve the discussions' do
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index 9e9e214060f..5c291f7b817 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -9,14 +9,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
describe 'As a user with access to the project' do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in user
+ visit project_merge_request_path(project, merge_request)
end
context 'with the internal tracker disabled' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to create a new issue' do
@@ -43,14 +43,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
end
it 'has a link to create a new issue for a discussion' do
- new_issue_link = new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+ new_issue_link = new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
expect(page).to have_link 'Resolve this discussion in a new issue', href: new_issue_link
end
context 'creating the issue' do
before do
- click_link 'Resolve this discussion in a new issue', href: new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+ click_link 'Resolve this discussion in a new issue', href: new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it 'has a hidden field for the discussion' do
@@ -66,10 +66,9 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
describe 'as a reporter' do
before do
project.team << [user, :reporter]
- gitlab_sign_in user
- visit new_namespace_project_issue_path(project.namespace, project,
- merge_request_to_resolve_discussions_of: merge_request.iid,
- discussion_to_resolve: discussion.id)
+ sign_in user
+ visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id)
end
it 'Shows a notice to ask someone else to resolve the discussions' do
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 96f6739af2d..2765d5448a4 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -23,10 +23,10 @@ describe 'Dropdown assignee', :feature, :js do
project.team << [user, :master]
project.team << [user_john, :master]
project.team << [user_jacob, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 5ee824c662a..98b1c5ee1b5 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -31,10 +31,10 @@ describe 'Dropdown author', js: true, feature: true do
project.team << [user, :master]
project.team << [user_john, :master]
project.team << [user_jacob, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index a05e4394ffd..fdc003f81b3 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -14,10 +14,10 @@ describe 'Dropdown hint', :js, :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index aec9d7ceb5d..26a0320675f 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -34,10 +34,10 @@ describe 'Dropdown label', js: true, feature: true do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'keyboard navigation' do
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index b21f41946b7..7c74d8dffff 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -30,10 +30,10 @@ describe 'Dropdown milestone', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 863f8f75cd8..9fc6391fa98 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -89,7 +89,7 @@ describe 'Filter issues', js: true, feature: true do
milestone: future_milestone,
project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'filter issues by author' do
@@ -459,7 +459,7 @@ describe 'Filter issues', js: true, feature: true do
context 'issue label clicked' do
before do
- find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click
+ find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click
end
it 'filters' do
@@ -804,7 +804,7 @@ describe 'Filter issues', js: true, feature: true do
describe 'RSS feeds' do
it 'updates atom feed link for project issues' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id)
+ visit project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id)
link = find_link('Subscribe')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
@@ -836,7 +836,7 @@ describe 'Filter issues', js: true, feature: true do
context 'URL has a trailing slash' do
before do
- visit "#{namespace_project_issues_path(project.namespace, project)}/"
+ visit "#{project_issues_path(project)}/"
end
it 'milestone dropdown loads milestones' do
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 09f228bcf49..4a91ce4be07 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -22,7 +22,7 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searching adds to recent searches' do
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
@@ -35,8 +35,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'visiting URL with search params adds to recent searches' do
- visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'foo', search: 'bar')
- visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'qux', search: 'garply')
+ 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)
@@ -48,7 +48,7 @@ describe 'Recent searches', js: true, feature: true do
it 'saved recent searches are restored last on the list' do
set_recent_searches(project_1_local_storage_key, '["saved1", "saved2"]')
- visit namespace_project_issues_path(project_1.namespace, project_1, search: 'foo')
+ visit project_issues_path(project_1, search: 'foo')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -59,12 +59,12 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searches are scoped to projects' do
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
- visit namespace_project_issues_path(project_2.namespace, project_2)
+ visit project_issues_path(project_2)
input_filtered_search('more', submit: true)
input_filtered_search('things', submit: true)
@@ -78,7 +78,7 @@ describe 'Recent searches', js: true, feature: true do
it 'clicking item fills search input' do
set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
wait_for_filtered_search('foo')
@@ -88,7 +88,7 @@ describe 'Recent searches', js: true, feature: true do
it 'clear recent searches button, clears recent searches' do
set_recent_searches(project_1_local_storage_key, '["foo"]')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
items_before = all('.filtered-search-history-dropdown-item', visible: false)
@@ -102,7 +102,7 @@ describe 'Recent searches', js: true, feature: true do
it 'shows flash error when failed to parse saved history' do
set_recent_searches(project_1_local_storage_key, 'fail')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches')
end
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 806c732b935..b16c5c280c7 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -9,10 +9,10 @@ describe 'Search bar', js: true, feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
def get_left_style(style)
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 22488f34813..a15c3d1d447 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -25,10 +25,10 @@ describe 'Visual tokens', js: true, feature: true do
before do
project.add_user(user, :master)
project.add_user(user_rock, :master)
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'editing author token' do
@@ -133,7 +133,7 @@ describe 'Visual tokens', js: true, feature: true do
describe 'editing milestone token' do
before do
input_filtered_search('milestone:%10.0 author:none', submit: false)
- first('.tokens-container .filtered-search-token').double_click
+ first('.tokens-container .filtered-search-token').click
first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item')
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index b369ef1ff79..05742004f06 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'New/edit issue', :feature, :js do
- include GitlabRoutingHelper
include ActionView::Helpers::JavaScriptHelper
include FormHelper
@@ -16,30 +15,30 @@ describe 'New/edit issue', :feature, :js do
before do
project.team << [user, :master]
project.team << [user2, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new issue' do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
- describe 'shorten users API pagination limit (CE)' do
+ describe 'shorten users API pagination limit' do
before do
# Using `allow_any_instance_of`/`and_wrap_original`, `original` would
# somehow refer to the very block we defined to _wrap_ that method, instead of
# the original method, resulting in infinite recurison when called.
# This is likely a bug with helper modules included into dynamically generated view classes.
# To work around this, we have to hold on to and call to the original implementation manually.
- original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options)
- allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
+ original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
+ allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
options = original_issue_dropdown_options.bind(original.receiver).call(*args)
options[:data][:per_page] = 2
options
end
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
click_button 'Unassigned'
@@ -64,7 +63,7 @@ describe 'New/edit issue', :feature, :js do
end
end
- describe 'single assignee (CE)' do
+ describe 'single assignee' do
before do
click_button 'Unassigned'
@@ -221,7 +220,7 @@ describe 'New/edit issue', :feature, :js do
context 'edit issue' do
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
end
it 'allows user to update issue' do
@@ -282,7 +281,7 @@ describe 'New/edit issue', :feature, :js do
before do
sub_group_project.add_master(user)
- visit new_namespace_project_issue_path(sub_group_project.namespace, sub_group_project)
+ visit new_project_issue_path(sub_group_project)
end
it 'creates new label from dropdown' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index e61eb5233d0..9b4cc653af5 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -8,12 +8,24 @@ feature 'GFM autocomplete', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
wait_for_requests
end
+ it 'updates issue descripton with GFM reference' do
+ find('.issuable-edit').click
+
+ find('#issue-description').native.send_keys("@#{user.name[0...3]}")
+
+ find('.atwho-view .cur').trigger('click')
+
+ click_button 'Save changes'
+
+ expect(find('.description')).to have_content(user.to_reference)
+ end
+
it 'opens autocomplete menu when field starts with text' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index fc8515cfe9b..5531a662c67 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -6,13 +6,9 @@ describe 'Group label on issue', :feature do
project = create(:empty_project, :public, namespace: group)
feature = create(:group_label, group: group, title: 'feature')
issue = create(:labeled_issue, project: project, labels: [feature])
- label_link = namespace_project_issues_path(
- project.namespace,
- project,
- label_name: [feature.name]
- )
+ label_link = project_issues_path(project, label_name: [feature.name])
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
link = find('.issuable-show-labels a')
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 163bc4bb32f..f75d2c72672 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -10,7 +10,7 @@ feature 'Issue Sidebar', feature: true do
let!(:label) { create(:label, project: project, title: 'bug') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'assignee', js: true do
@@ -154,20 +154,6 @@ feature 'Issue Sidebar', feature: true do
end
end
- context 'as a allowed mobile user', js: true do
- before do
- project.team << [user, :developer]
- resize_screen_xs
- visit_issue(project, issue)
- end
-
- context 'mobile sidebar' do
- it 'collapses the sidebar for small screens' do
- expect(page).not_to have_css('aside.right-sidebar.right-sidebar-collapsed')
- end
- end
- end
-
context 'as a guest' do
before do
project.team << [user, :guest]
@@ -180,7 +166,7 @@ feature 'Issue Sidebar', feature: true do
end
def visit_issue(project, issue)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
def open_issue_sidebar
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index 66d823ec9d0..affba35f61c 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -6,9 +6,9 @@ feature 'Issue markdown toolbar', feature: true, js: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it "doesn't include first new line when adding bold" do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 21a7637fe7f..833eb47efb2 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -9,7 +9,7 @@ feature 'issue move to another project' do
create(:issue, description: text, project: old_project, author: user)
end
- background { gitlab_sign_in(user) }
+ background { sign_in(user) }
context 'user does not have permission to move issue' do
background do
@@ -41,13 +41,10 @@ feature 'issue move to another project' do
find('#issuable-move', visible: false).set(new_project.id)
click_button('Save changes')
- wait_for_requests
-
- expect(current_url).to include project_path(new_project)
-
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
expect(page).to have_content(issue.title)
+ expect(page.current_path).to include project_path(new_project)
end
scenario 'searching project dropdown', js: true do
@@ -98,10 +95,6 @@ feature 'issue move to another project' do
end
def issue_path(issue)
- namespace_project_issue_path(issue.project.namespace, issue.project, issue)
- end
-
- def project_path(project)
- namespace_project_path(new_project.namespace, new_project)
+ project_issue_path(issue.project, issue)
end
end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index bd31e44ef33..184cde5b9c5 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -8,7 +8,7 @@ feature 'Issue notes polling', :feature, :js do
describe 'creates' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'displays the new comment' do
@@ -27,8 +27,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
@@ -93,8 +93,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) }
before do
- gitlab_sign_in(user2)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user2)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
@@ -114,8 +114,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:system_note) { create(:system_note, noteable: issue, project: project, author: user, note: note_text) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index f648295416f..6fb103e5477 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -9,8 +9,8 @@ describe 'Create notes on issues', :js, :feature do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
fill_in 'note[note]', with: note_text
click_button 'Comment'
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 57c783790b5..39a458fe3d0 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -18,14 +18,14 @@ describe 'New issue', feature: true, js: true do
)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when identified as a spam' do
before do
WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: "true", status: 200)
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'creates an issue after solving reCaptcha' do
@@ -50,7 +50,7 @@ describe 'New issue', feature: true, js: true do
before do
WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: 'false', status: 200)
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'creates an issue' do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index a1c00dd64f6..f57b58f68e3 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -7,8 +7,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'creates todo when clicking button' do
@@ -21,7 +21,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
expect(page).to have_content '1'
end
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within '.header-content .todos-count' do
expect(page).to have_content '1'
@@ -36,7 +36,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
expect(page).to have_selector('.todos-count', visible: false)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_selector('.todos-count', visible: false)
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index dc981406e4e..5a7c4f54cb6 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,18 +1,18 @@
require 'rails_helper'
-feature 'Multiple issue updating from issues#index', feature: true do
+feature 'Multiple issue updating from issues#index', :js do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
- context 'status', js: true do
+ context 'status' do
it 'sets to closed' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -25,7 +25,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'sets to open' do
create_closed
- visit namespace_project_issues_path(project.namespace, project, state: 'closed')
+ visit project_issues_path(project, state: 'closed')
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -37,9 +37,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
end
end
- context 'assignee', js: true do
+ context 'assignee' do
it 'updates to current user' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -55,7 +55,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'updates to unassigned' do
create_assigned
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -67,11 +67,11 @@ feature 'Multiple issue updating from issues#index', feature: true do
end
end
- context 'milestone', js: true do
- let(:milestone) { create(:milestone, project: project) }
+ context 'milestone' do
+ let!(:milestone) { create(:milestone, project: project) }
it 'updates milestone' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -85,7 +85,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'sets to no milestone' do
create_with_milestone
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(first('.issue')).to have_content milestone.title
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 168cdd08137..1cd1f016674 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -13,8 +13,8 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
after do
@@ -42,8 +42,8 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(guest)
+ visit project_issue_path(project, issue)
end
it 'does not create a note, and sets the due date accordingly' do
@@ -82,8 +82,8 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(guest)
+ visit project_issue_path(project, issue)
end
it 'does not create a note, and sets the due date accordingly' do
@@ -108,7 +108,7 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
context 'Issue' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like 'issuable time tracker'
@@ -118,33 +118,7 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
let(:merge_request) { create(:merge_request, source_project: project) }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
-
- it_behaves_like 'issuable time tracker'
- end
- end
-
- describe 'Issuable time tracking' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- project.team << [user, :developer]
- end
-
- context 'Issue' do
- before do
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
- context 'Merge Request' do
- let(:merge_request) { create(:merge_request, source_project: project) }
-
- before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'issuable time tracker'
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index f47b89fd718..0016fa10f67 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -24,7 +24,7 @@ describe 'Issues', feature: true do
end
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
find('.js-zen-enter').click
end
@@ -42,7 +42,7 @@ describe 'Issues', feature: true do
end
it 'allows user to select unassigned', js: true do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
expect(page).to have_content "Assignee #{user.name}"
@@ -62,7 +62,7 @@ describe 'Issues', feature: true do
describe 'due date', js: true do
context 'on new form' do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'saves with due date' do
@@ -90,7 +90,7 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
end
it 'saves with due date' do
@@ -135,7 +135,7 @@ describe 'Issues', feature: true do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar')
create(:award_emoji, awardable: issue)
- visit namespace_project_issues_path(project.namespace, project, assignee_id: user.id)
+ visit project_issues_path(project, assignee_id: user.id)
expect(page).to have_content 'foobar'
expect(page.all('.no-comments').first.text).to eq "0"
@@ -161,7 +161,7 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
it 'allows filtering by issues with no specified assignee' do
- visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
+ visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
@@ -169,7 +169,7 @@ describe 'Issues', feature: true do
end
it 'allows filtering by a specified assignee' do
- visit namespace_project_issues_path(project.namespace, project, assignee_id: user.id)
+ visit project_issues_path(project, assignee_id: user.id)
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
@@ -190,14 +190,14 @@ describe 'Issues', feature: true do
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
it 'sorts by newest' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
+ visit project_issues_path(project, sort: sort_value_recently_created)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
it 'sorts by oldest' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
+ visit project_issues_path(project, sort: sort_value_oldest_created)
expect(first_issue).to include('baz')
expect(last_issue).to include('foo')
@@ -206,7 +206,7 @@ describe 'Issues', feature: true do
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_updated)
+ visit project_issues_path(project, sort: sort_value_recently_updated)
expect(first_issue).to include('baz')
end
@@ -214,7 +214,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently updated' do
baz.updated_at = Time.now - 100
baz.save
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_updated)
+ visit project_issues_path(project, sort: sort_value_oldest_updated)
expect(first_issue).to include('baz')
end
@@ -226,13 +226,13 @@ describe 'Issues', feature: true do
end
it 'sorts by recently due date' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_soon)
+ visit project_issues_path(project, sort: sort_value_due_date_soon)
expect(first_issue).to include('foo')
end
it 'sorts by least recently due date' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+ visit project_issues_path(project, sort: sort_value_due_date_later)
expect(first_issue).to include('bar')
end
@@ -240,7 +240,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently due date by excluding nil due dates' do
bar.update(due_date: nil)
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+ visit project_issues_path(project, sort: sort_value_due_date_later)
expect(first_issue).to include('foo')
end
@@ -255,7 +255,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently due date by excluding nil due dates' do
bar.update(due_date: nil)
- visit namespace_project_issues_path(project.namespace, project, label_names: [label.name], sort: sort_value_due_date_later)
+ visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
expect(first_issue).to include('foo')
end
@@ -269,7 +269,7 @@ describe 'Issues', feature: true do
end
it 'filters by none' do
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::NoDueDate.name)
+ visit project_issues_path(project, due_date: Issue::NoDueDate.name)
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
@@ -277,7 +277,7 @@ describe 'Issues', feature: true do
end
it 'filters by any' do
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::AnyDueDate.name)
+ visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -289,7 +289,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today.end_of_week)
baz.update(due_date: Date.today - 8.days)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisWeek.name)
+ visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -301,7 +301,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today.end_of_month)
baz.update(due_date: Date.today - 50.days)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisMonth.name)
+ visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -313,7 +313,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today + 20.days)
baz.update(due_date: Date.yesterday)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::Overdue.name)
+ visit project_issues_path(project, due_date: Issue::Overdue.name)
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
@@ -330,14 +330,14 @@ describe 'Issues', feature: true do
end
it 'sorts by recently due milestone' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
+ visit project_issues_path(project, sort: sort_value_milestone_soon)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
it 'sorts by least recently due milestone' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
+ visit project_issues_path(project, sort: sort_value_milestone_later)
expect(first_issue).to include('bar')
expect(last_issue).to include('baz')
@@ -355,7 +355,7 @@ describe 'Issues', feature: true do
end
it 'sorts with a filter applied' do
- visit namespace_project_issues_path(project.namespace, project,
+ visit project_issues_path(project,
sort: sort_value_oldest_created,
assignee_id: user2.id)
@@ -397,7 +397,7 @@ describe 'Issues', feature: true do
let!(:label) { create(:label, project: project) }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'will not send ajax request when no data is changed' do
@@ -416,7 +416,7 @@ describe 'Issues', feature: true do
context 'by authorized user' do
it 'allows user to select unassigned', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.assignee') do
expect(page).to have_content "#{user.name}"
@@ -435,7 +435,7 @@ describe 'Issues', feature: true do
it 'allows user to select an assignee', js: true do
issue2 = create(:issue, project: project, author: user)
- visit namespace_project_issue_path(project.namespace, project, issue2)
+ visit project_issue_path(project, issue2)
page.within('.assignee') do
expect(page).to have_content "No assignee"
@@ -456,7 +456,7 @@ describe 'Issues', feature: true do
it 'allows user to unselect themselves', js: true do
issue2 = create(:issue, project: project, author: user)
- visit namespace_project_issue_path(project.namespace, project, issue2)
+ visit project_issue_path(project, issue2)
page.within '.assignee' do
click_link 'Edit'
@@ -487,7 +487,7 @@ describe 'Issues', feature: true do
sign_out(:user)
sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_content issue.assignees.first.name
end
end
@@ -499,7 +499,7 @@ describe 'Issues', feature: true do
context 'by authorized user' do
it 'allows user to select unassigned', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.milestone') do
expect(page).to have_content "None"
@@ -517,7 +517,7 @@ describe 'Issues', feature: true do
end
it 'allows user to de-select milestone', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.milestone') do
click_link 'Edit'
@@ -550,7 +550,7 @@ describe 'Issues', feature: true do
sign_out(:user)
sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_content milestone.title
end
end
@@ -565,23 +565,21 @@ describe 'Issues', feature: true do
end
it 'redirects to signin then back to new issue after signin' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_link 'New issue'
expect(current_path).to eq new_user_session_path
- # NOTE: This is specifically testing the redirect after login, so we
- # need the full login flow
gitlab_sign_in(create(:user))
- expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project)
+ expect(current_path).to eq new_project_issue_path(project)
end
end
context 'dropzone upload file', js: true do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'uploads file when dragging into textarea' do
@@ -608,7 +606,7 @@ describe 'Issues', feature: true do
message: 'added issue template',
branch_name: 'master')
- visit new_namespace_project_issue_path(project.namespace, project, issuable_template: 'bug')
+ visit new_project_issue_path(project, issuable_template: 'bug')
end
it 'fills in template' do
@@ -625,7 +623,7 @@ describe 'Issues', feature: true do
project.issues << issue
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button('Email a new issue')
end
@@ -654,7 +652,7 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'adds due date to issue' do
@@ -698,7 +696,7 @@ describe 'Issues', feature: true do
it 'updates the title', js: true do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title')
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_text("new title")
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
index cb835f533e0..985f42e484c 100644
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ b/spec/features/merge_requests/assign_issues_spec.rb
@@ -13,8 +13,8 @@ feature 'Merge request issue assignment', js: true, feature: true do
end
def visit_merge_request(current_user = nil)
- gitlab_sign_in(current_user || user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(current_user || user)
+ visit project_merge_request_path(project, merge_request)
end
context 'logged in as author' do
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
index e9dd755b6af..3b01c763281 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_requests/award_spec.rb
@@ -7,8 +7,8 @@ feature 'Merge request awards', js: true, feature: true do
describe 'logged in' do
before do
- gitlab_sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'adds award to merge request' do
@@ -16,7 +16,7 @@ feature 'Merge request awards', js: true, feature: true do
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(first('.js-emoji-btn')).to have_content '1'
end
@@ -25,7 +25,7 @@ feature 'Merge request awards', js: true, feature: true do
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(first('.js-emoji-btn')).to have_content '0'
end
@@ -39,7 +39,7 @@ feature 'Merge request awards', js: true, feature: true do
describe 'logged out' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not see award menu button' do
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
index 060cfb8fdd1..f2d6c0d9769 100644
--- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
+++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
@@ -6,7 +6,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru
let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
@@ -64,6 +64,6 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb
index 6ba96570e3d..e4c33d57e8d 100644
--- a/spec/features/merge_requests/cherry_pick_spec.rb
+++ b/spec/features/merge_requests/cherry_pick_spec.rb
@@ -7,7 +7,7 @@ describe 'Cherry-pick Merge Requests', js: true do
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
@@ -28,7 +28,7 @@ describe 'Cherry-pick Merge Requests', js: true do
end
it "doesn't show a Cherry-pick button" do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link "Cherry-pick"
end
@@ -36,7 +36,7 @@ describe 'Cherry-pick Merge Requests', js: true do
context "With a merge commit" do
it "shows a Cherry-pick button" do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).to have_link "Cherry-pick"
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index 365b2555c35..527837b56be 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -20,9 +20,9 @@ feature 'Merge Request closing issues message', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
end
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 9c091befa27..3e01ea69122 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -79,14 +79,14 @@ feature 'Merge request conflict resolution', js: true, feature: true do
context 'can be resolved in the UI' do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'the conflicts are resolvable' do
let(:merge_request) { create_merge_request('conflict-resolvable') }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a link to the conflict resolution page' do
@@ -117,7 +117,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
click_link('conflicts', href: /\/conflicts\Z/)
end
@@ -164,9 +164,9 @@ feature 'Merge request conflict resolution', js: true, feature: true do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to the conflict resolution page' do
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 8f7adbccaaa..e0d97dec586 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -7,11 +7,11 @@ feature 'Create New Merge Request', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
it 'selects the source branch sha when a tag with the same name exists' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
click_link 'New merge request'
expect(page).to have_content('Source branch')
@@ -24,7 +24,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'selects the target branch sha when a tag with the same name exists' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
click_link 'New merge request'
@@ -38,7 +38,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'generates a diff for an orphaned branch' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
expect(page).to have_content('Source branch')
@@ -65,7 +65,7 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'does not leak the private project name & namespace' do
private_project = create(:project, :private)
- visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+ visit project_new_merge_request_path(project, merge_request: { target_project_id: private_project.id })
expect(page).not_to have_content private_project.path_with_namespace
expect(page).to have_content project.path_with_namespace
@@ -76,7 +76,7 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'does not leak the private project name & namespace' do
private_project = create(:project, :private)
- visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id })
+ visit project_new_merge_request_path(project, merge_request: { source_project_id: private_project.id })
expect(page).not_to have_content private_project.path_with_namespace
expect(page).to have_content project.path_with_namespace
@@ -84,13 +84,13 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'populates source branch button' do
- visit new_namespace_project_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
expect(find('.js-source-branch')).to have_content('fix')
end
it 'allows to change the diff view' do
- visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'fix' })
click_link 'Changes'
@@ -106,7 +106,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'does not allow non-existing branches' do
- visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
expect(page).to have_content('The form contains the following errors')
expect(page).to have_content('Source branch "non-exist-source" does not exist')
@@ -115,7 +115,7 @@ feature 'Create New Merge Request', feature: true, js: true do
context 'when a branch contains commits that both delete and add the same image' do
it 'renders the diff successfully' do
- visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
click_link "Changes"
@@ -125,7 +125,7 @@ feature 'Create New Merge Request', feature: true, js: true do
# Isolates a regression (see #24627)
it 'does not show error messages on initial form' do
- visit new_namespace_project_merge_request_path(project.namespace, project)
+ visit project_new_merge_request_path(project)
expect(page).not_to have_selector('#error_explanation')
expect(page).not_to have_content('The form contains the following error')
end
@@ -138,8 +138,8 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'shows pipelines for a new merge request' do
- visit new_namespace_project_merge_request_path(
- project.namespace, project,
+ visit project_new_merge_request_path(
+ project,
merge_request: { target_branch: 'master', source_branch: 'fix' })
page.within('.merge-request') do
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index 69059dfa562..9b7795ace62 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -16,7 +16,7 @@ feature 'Merge request created from fork' do
background do
fork_project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
scenario 'user can access merge request' do
@@ -64,7 +64,6 @@ feature 'Merge request created from fork' do
end
def visit_merge_request(mr)
- visit namespace_project_merge_request_path(project.namespace,
- project, mr)
+ visit project_merge_request_path(project, mr)
end
end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index f2af3198319..8d7160e2df2 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -8,14 +8,10 @@ describe 'Deleted source branch', feature: true, js: true do
let(:merge_request) { create(:merge_request) }
before do
- gitlab_sign_in user
+ sign_in user
merge_request.project.team << [user, :master]
merge_request.update!(source_branch: 'this-branch-does-not-exist')
- visit namespace_project_merge_request_path(
- merge_request.project.namespace,
- merge_request.project,
- merge_request
- )
+ visit project_merge_request_path(merge_request.project, merge_request)
end
it 'shows a message about missing source branch' do
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 989dfb71d10..4fc70027193 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -20,12 +20,12 @@ feature 'Diff note avatars', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'discussion tab' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show avatars on discussion tab' do
@@ -50,7 +50,7 @@ feature 'Diff note avatars', feature: true, js: true do
context 'commit view' do
before do
- visit namespace_project_commit_path(project.namespace, project, merge_request.commits.first.id)
+ visit project_commit_path(project, merge_request.commits.first.id)
end
it 'does not render avatar after commenting' do
@@ -65,7 +65,7 @@ feature 'Diff note avatars', feature: true, js: true do
wait_for_requests
end
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).to have_content('test comment')
expect(page).not_to have_selector('.js-avatar-container')
@@ -76,7 +76,7 @@ feature 'Diff note avatars', feature: true, js: true do
%w(inline parallel).each do |view|
context "#{view} view" do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+ visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
end
@@ -168,7 +168,7 @@ feature 'Diff note avatars', feature: true, js: true do
before do
create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+ visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 0f8ca6f90d1..93e2d134389 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -19,7 +19,7 @@ feature 'Diff notes resolve', feature: true, js: true do
context 'no discussions' do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
note.destroy
visit_merge_request
end
@@ -33,7 +33,7 @@ feature 'Diff notes resolve', feature: true, js: true do
context 'as authorized user' do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit_merge_request
end
@@ -402,7 +402,7 @@ feature 'Diff notes resolve', feature: true, js: true do
before do
project.team << [guest, :guest]
- gitlab_sign_in guest
+ sign_in guest
end
context 'someone elses merge request' do
@@ -494,6 +494,6 @@ feature 'Diff notes resolve', feature: true, js: true do
def visit_merge_request(mr = nil)
mr = mr || merge_request
- visit namespace_project_merge_request_path(mr.project.namespace, mr.project, mr)
+ visit project_merge_request_path(mr.project, mr)
end
end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index cb6cd6571a8..d9de4e388d5 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -12,7 +12,7 @@ feature 'Diffs URL', js: true, feature: true do
it 'renders the notes' do
create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master'
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
# Load notes and diff through AJAX
expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
@@ -26,7 +26,7 @@ feature 'Diffs URL', js: true, feature: true do
let(:fragment) { "#note_#{note.id}" }
before do
- visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
end
it 'shows expanded note' do
@@ -39,7 +39,7 @@ feature 'Diffs URL', js: true, feature: true do
let(:fragment) { "#note_#{note.id}" }
before do
- visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
end
it 'shows expanded note' do
@@ -52,7 +52,7 @@ feature 'Diffs URL', js: true, feature: true do
it 'displays warning' do
allow(Commit).to receive(:max_diff_options).and_return(max_files: 3)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
page.within('.alert') do
expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
@@ -74,8 +74,8 @@ feature 'Diffs URL', js: true, feature: true do
context 'as author' do
it 'shows direct edit link' do
- gitlab_sign_in(author_user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(author_user)
+ visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob")
@@ -84,8 +84,8 @@ feature 'Diffs URL', js: true, feature: true do
context 'as user who needs to fork' do
it 'shows fork/cancel confirmation' do
- gitlab_sign_in(user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ 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").click
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index 88ae257236c..55846f8609b 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
feature 'Merge Request Discussions', feature: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe "Diff discussions" do
@@ -27,13 +27,13 @@ feature 'Merge Request Discussions', feature: true do
let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
before(:each) do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'active discussions' do
it 'shows a link to the diff' do
within(".discussion[data-discussion-id='#{active_discussion.id}']") do
- path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: active_discussion.line_code)
+ path = diffs_project_merge_request_path(project, merge_request, anchor: active_discussion.line_code)
expect(page).to have_link('the diff', href: path)
end
end
@@ -42,7 +42,7 @@ feature 'Merge Request Discussions', feature: true do
context 'outdated discussions' do
it 'shows a link to the outdated diff' do
within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
- path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
+ path = diffs_project_merge_request_path(project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
expect(page).to have_link('an old version of the diff', href: path)
end
end
@@ -72,7 +72,7 @@ feature 'Merge Request Discussions', feature: true do
end
before(:each) do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'a regular commit comment' do
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index 804bf6967d6..b7063f35546 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -8,9 +8,9 @@ feature 'Edit Merge Request', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
context 'editing a MR' do
@@ -33,7 +33,7 @@ feature 'Edit Merge Request', feature: true do
merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
uncheck 'Remove source branch when merge request is accepted'
click_button 'Save changes'
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 9b677aeca0a..754f82900e4 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -26,9 +26,9 @@ feature 'Issue filtering by Labels', feature: true, js: true do
mr3.labels << feature
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
context 'filter by label bug' do
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 79bca0c9de2..d2af150d852 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -15,7 +15,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'filters by no Milestone', js: true do
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index c12edf1fdf3..2a161b83aa0 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -14,10 +14,10 @@ describe 'Filter merge requests', feature: true do
before do
project.team << [user, :master]
group.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
create(:merge_request, source_project: project, target_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
describe 'for assignee from mr#index' do
@@ -191,7 +191,7 @@ describe 'Filter merge requests', feature: true do
assignee: user)
mr.labels << bug_label
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
context 'only text', js: true do
@@ -275,7 +275,7 @@ describe 'Filter merge requests', feature: true do
mr1.labels << bug_label
mr2.labels << bug_label
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'is able to filter and sort merge requests' do
@@ -297,7 +297,7 @@ describe 'Filter merge requests', feature: true do
describe 'filter by assignee id', js: true do
it 'filter by current user' do
- visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
+ visit project_merge_requests_path(project, assignee_id: user.id)
expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
expect_filtered_search_input_empty
@@ -307,7 +307,7 @@ describe 'Filter merge requests', feature: true do
new_user = create(:user)
project.add_developer(new_user)
- visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
+ visit project_merge_requests_path(project, assignee_id: new_user.id)
expect_tokens([{ name: 'assignee', value: "@#{new_user.username}" }])
expect_filtered_search_input_empty
@@ -316,7 +316,7 @@ describe 'Filter merge requests', feature: true do
describe 'filter by author id', js: true do
it 'filter by current user' do
- visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
+ visit project_merge_requests_path(project, author_id: user.id)
expect_tokens([{ name: 'author', value: "@#{user.username}" }])
expect_filtered_search_input_empty
@@ -326,7 +326,7 @@ describe 'Filter merge requests', feature: true do
new_user = create(:user)
project.add_developer(new_user)
- visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
+ visit project_merge_requests_path(project, author_id: new_user.id)
expect_tokens([{ name: 'author', value: "@#{new_user.username}" }])
expect_filtered_search_input_empty
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 1996c2fa09a..171386e16ad 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'New/edit merge request', feature: true, js: true do
- include GitlabRoutingHelper
-
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:fork_project) { create(:project, forked_from_project: project) }
let!(:user) { create(:user)}
@@ -18,13 +16,12 @@ describe 'New/edit merge request', feature: true, js: true do
context 'owned projects' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit new_namespace_project_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
@@ -114,7 +111,7 @@ describe 'New/edit merge request', feature: true, js: true do
target_branch: 'master'
)
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
it 'updates merge request' do
@@ -177,13 +174,12 @@ describe 'New/edit merge request', feature: true, js: true do
context 'forked project' do
before do
fork_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit new_namespace_project_merge_request_path(
- fork_project.namespace,
+ visit project_new_merge_request_path(
fork_project,
merge_request: {
source_project_id: fork_project.id,
@@ -251,7 +247,7 @@ describe 'New/edit merge request', feature: true, js: true do
target_branch: 'master'
)
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
it 'should update merge request' do
diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
index 27ba380b005..6cd62ecec72 100644
--- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
+++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
@@ -34,9 +34,9 @@ feature 'Clicking toggle commit message link', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_selector('.js-commit-message')
click_button "Modify commit message"
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index 8af7d985036..d3475bee5cc 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -28,8 +28,8 @@ feature 'Merge immediately', :feature, :js do
end
before do
- gitlab_sign_in user
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ sign_in user
+ visit project_merge_request_path(merge_request.project, merge_request)
end
it 'enables merge immediately' do
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index bfadd7cb81a..230b04296b3 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -28,7 +28,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
visit_merge_request(merge_request)
end
@@ -121,7 +121,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
visit_merge_request(merge_request)
end
@@ -155,6 +155,6 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 7664fbfbb4c..4adf72a60b0 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -11,12 +11,12 @@ feature 'Mini Pipeline Graph', :js, :feature do
before do
build.run
- gitlab_sign_in(user)
+ sign_in(user)
visit_merge_request
end
def visit_merge_request(format = :html)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request, format: format)
+ visit project_merge_request_path(project, merge_request, format: format)
end
it 'should display a mini pipeline graph' do
@@ -111,7 +111,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
build_item.click
find('.build-page')
- expect(current_path).to eql(namespace_project_job_path(project.namespace, project, build))
+ expect(current_path).to eql(project_job_path(project, build))
end
it 'should show tooltip when hovered' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 5cd9a7fbe26..651cb9d86fb 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -5,7 +5,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
let(:project) { merge_request.target_project }
before do
- gitlab_sign_in merge_request.author
+ sign_in merge_request.author
project.team << [merge_request.author, :master]
end
@@ -145,6 +145,6 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index c2241317e04..837366ced3c 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -7,7 +7,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'with pipelines' do
@@ -19,7 +19,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'user visits merge request pipelines tab' do
@@ -34,7 +34,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
context 'without pipelines' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'user visits merge request page' do
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
index 4328d66c748..3ed76926eab 100644
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ b/spec/features/merge_requests/target_branch_spec.rb
@@ -6,14 +6,11 @@ describe 'Target branch', feature: true, js: true do
let(:project) { merge_request.project }
def path_to_merge_request
- namespace_project_merge_request_path(
- project.namespace,
- project, merge_request
- )
+ project_merge_request_path(project, merge_request)
end
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
index cba9a2cda99..912aa34b0c8 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
@@ -2,10 +2,10 @@ require 'spec_helper'
feature 'Toggle Whitespace Changes', js: true, feature: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
merge_request = create(:merge_request)
project = merge_request.source_project
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
end
it 'has a button to toggle whitespace changes' do
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
index c4c06e9a7a0..01251105f72 100644
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -8,10 +8,10 @@ feature 'toggler_behavior', js: true, feature: true do
let(:fragment_id) { "#note_#{note.id}" }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
project = merge_request.source_project
page.current_window.resize_to(1000, 300)
- visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+ visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
end
describe 'scroll position' do
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index d0418c74699..43153e2cfa4 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -7,13 +7,13 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'status', js: true do
describe 'close merge request' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'closes merge request' do
@@ -26,7 +26,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
describe 'reopen merge request' do
before do
merge_request.close
- visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed')
+ visit project_merge_requests_path(project, state: 'closed')
end
it 'reopens merge request' do
@@ -40,7 +40,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
context 'assignee', js: true do
describe 'set assignee' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "updates merge request with assignee" do
@@ -56,7 +56,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
before do
merge_request.assignee = user
merge_request.save
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "removes assignee from the merge request" do
@@ -72,7 +72,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
describe 'set milestone' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "updates merge request with milestone" do
@@ -86,7 +86,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
before do
merge_request.milestone = milestone
merge_request.save
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "removes milestone from the merge request" do
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index cabb8e455f9..f541f495995 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -37,7 +37,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
it 'filters on no assignee' do
visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
- expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
+ expect(current_path).to eq(project_merge_requests_path(project))
expect(page).to have_content 'merge_lfs'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
@@ -136,7 +136,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
end
it 'sorts by recently due milestone' do
- visit namespace_project_merge_requests_path(project.namespace, project,
+ visit project_merge_requests_path(project,
label_name: [label.name, label2.name],
assignee_id: user.id,
sort: sort_value_milestone_soon)
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 ac7e0eb2727..1cfd78663e5 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -7,7 +7,7 @@ feature 'Merge requests > User posts diff notes', :js do
before do
project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
let(:comment_button_class) { '.add-diff-note' }
@@ -17,7 +17,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over a parallel view diff file' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'parallel')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'parallel')
end
context 'with an old line on the left and no line on the right' do
@@ -92,7 +92,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over an inline view diff file' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
@@ -136,9 +136,9 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over a diff discussion' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not allow commenting' do
@@ -149,7 +149,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when cancelling the comment addition' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
@@ -161,7 +161,7 @@ feature 'Merge requests > User posts diff notes', :js do
describe 'with muliple note forms' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
click_diff_line(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
end
@@ -181,7 +181,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when the MR only supports legacy diff notes' do
before do
merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 12f987e12ea..35ed08e0a5e 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -13,8 +13,8 @@ describe 'Merge requests > User posts notes', :js do
end
before do
- gitlab_sign_in :admin
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(create(:admin))
+ visit project_merge_request_path(project, merge_request)
end
subject { page }
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb
index 0d88a8172b0..624a425ae52 100644
--- a/spec/features/merge_requests/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb
@@ -11,11 +11,11 @@ feature 'Merge requests > User sees system notes' do
before do
user = create(:user)
private_project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows the system note' do
- visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+ visit project_merge_request_path(public_project, merge_request)
expect(page).to have_css('.system-note')
end
@@ -23,7 +23,7 @@ feature 'Merge requests > User sees system notes' do
context 'when not logged-in' do
it 'hides the system note' do
- visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+ visit project_merge_request_path(public_project, merge_request)
expect(page).not_to have_css('.system-note')
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index 71aa71e380e..434f5a7c0ac 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -16,8 +16,8 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
describe 'merge-request-only commands' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
after do
@@ -51,9 +51,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not change the WIP prefix' do
@@ -97,9 +97,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not merge the MR' do
@@ -125,13 +125,13 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
before do
- gitlab_sign_out
+ sign_out(:user)
another_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'changes target_branch in new merge_request' do
- visit new_namespace_project_merge_request_path(another_project.namespace, another_project, new_url_opts)
+ visit project_new_merge_request_path(another_project, new_url_opts)
fill_in "merge_request_title", with: 'My brand new feature'
fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:"
@@ -145,7 +145,7 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
it 'does not change target branch when merge request is edited' do
new_merge_request = create(:merge_request, source_project: another_project)
- visit edit_namespace_project_merge_request_path(another_project.namespace, another_project, new_merge_request)
+ visit edit_project_merge_request_path(another_project, new_merge_request)
fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n"
click_button "Save changes"
@@ -181,9 +181,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not change target branch' do
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 04a72d3be34..218d57b49e3 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -8,8 +8,8 @@ feature 'Merge Request versions', js: true, feature: true do
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
before do
- gitlab_sign_in :admin
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(create(:admin))
+ visit diffs_project_merge_request_path(project, merge_request)
end
it 'show the latest version of the diff' do
@@ -96,8 +96,7 @@ feature 'Merge Request versions', js: true, feature: true do
end
it 'has a path with comparison context' do
- expect(page).to have_current_path diffs_namespace_project_merge_request_path(
- project.namespace,
+ expect(page).to have_current_path diffs_project_merge_request_path(
project,
merge_request.iid,
diff_id: merge_request_diff3.id,
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index e82e69c5f4a..b0fe5f3e1cb 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -12,9 +12,9 @@ feature 'Widget Deployments Header', feature: true, js: true do
given!(:manual) { }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'displays that the environment is deployed' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 3ac1f603de6..46c558659c7 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -7,21 +7,19 @@ describe 'Merge request', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit new_namespace_project_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'feature',
target_branch: 'master'
- }
- )
+ })
end
it 'shows widget status after creating new merge request' do
@@ -44,7 +42,7 @@ describe 'Merge request', :feature, :js do
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows environments link' do
@@ -71,7 +69,7 @@ describe 'Merge request', :feature, :js do
type: 'CiService',
category: 'ci')
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has danger button while waiting for external CI status' do
@@ -92,7 +90,7 @@ describe 'Merge request', :feature, :js do
head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has danger button when not succeeded' do
@@ -112,9 +110,7 @@ describe 'Merge request', :feature, :js do
status: :manual,
head_pipeline_of: merge_request)
- visit namespace_project_merge_request_path(project.namespace,
- project,
- merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about blocked pipeline' do
@@ -136,7 +132,7 @@ describe 'Merge request', :feature, :js do
head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has info button when MWBS button' do
@@ -154,7 +150,7 @@ describe 'Merge request', :feature, :js do
merge_error: 'Something went wrong'
)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about the merge error' do
@@ -175,7 +171,7 @@ describe 'Merge request', :feature, :js do
merge_error: 'Something went wrong'
)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about the merge error' do
@@ -191,7 +187,7 @@ describe 'Merge request', :feature, :js do
context 'merge error' do
before do
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'updates the MR widget' do
@@ -209,10 +205,10 @@ describe 'Merge request', :feature, :js do
before do
project.team << [user2, :master]
- gitlab_sign_out
- gitlab_sign_in user2
+ sign_out(:user)
+ sign_in(user2)
merge_request.update(target_project: fork_project)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'user can merge into the source project' do
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb
index 72d001bf408..91cf8fc7218 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_requests/wip_message_spec.rb
@@ -6,21 +6,19 @@ feature 'Work In Progress help message', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'with WIP commits' do
it 'shows a specific WIP hint' do
- visit new_namespace_project_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'wip',
target_branch: 'master'
- }
- )
+ })
within_wip_explanation do
expect(page).to have_text(
@@ -32,16 +30,14 @@ feature 'Work In Progress help message', feature: true do
context 'without WIP commits' do
it 'shows the regular WIP message' do
- visit new_namespace_project_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'fix',
target_branch: 'master'
- }
- )
+ })
within_wip_explanation do
expect(page).not_to have_text(
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index 58989581ffe..ce0c27cbe77 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,17 +1,19 @@
require 'rails_helper'
feature 'Milestone', feature: true do
- let(:project) { create(:empty_project, :public) }
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
let(:user) { create(:user) }
before do
+ create(:group_member, group: group, user: user)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
feature 'Create a milestone' do
scenario 'shows an informative message for a new milestone' do
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
page.within '.milestone-form' do
fill_in "milestone_title", with: '8.7'
@@ -31,23 +33,36 @@ feature 'Milestone', feature: true do
milestone = create(:milestone, project: project, title: 8.7)
create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
end
end
- feature 'Open a milestone with an existing title' do
- scenario 'displays validation message' do
+ feature 'Open a project milestone with an existing title' do
+ scenario 'displays validation message when there is a project milestone with same title' do
milestone = create(:milestone, project: project, title: 8.7)
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
page.within '.milestone-form' do
fill_in "milestone_title", with: milestone.title
end
find('input[name="commit"]').click
- expect(find('.alert-danger')).to have_content('Title has already been taken')
+ expect(find('.alert-danger')).to have_content('already being used for another group or project milestone.')
+ end
+
+ scenario 'displays validation message when there is a group milestone with same title' do
+ milestone = create(:milestone, project_id: nil, group: project.group, title: 8.7)
+
+ visit new_group_milestone_path(project.group)
+
+ page.within '.milestone-form' do
+ fill_in "milestone_title", with: milestone.title
+ end
+ find('input[name="commit"]').click
+
+ expect(find('.alert-danger')).to have_content('already being used for another group or project milestone.')
end
end
end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index cdf6cfba402..626a1f35e62 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -9,11 +9,11 @@ describe 'Milestone show', feature: true do
before do
project.add_user(user, :developer)
- gitlab_sign_in(user)
+ sign_in(user)
end
def visit_milestone
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'avoids N+1 database queries' do
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
new file mode 100644
index 00000000000..42764e808e6
--- /dev/null
+++ b/spec/features/oauth_login_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+feature 'OAuth Login', js: true do
+ def enter_code(code)
+ fill_in 'user_otp_attempt', with: code
+ click_button 'Verify code'
+ end
+
+ def stub_omniauth_config(provider)
+ OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
+ Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ end
+
+ providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
+ :facebook, :cas3, :auth0, :authentiq]
+
+ before(:all) do
+ # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
+ # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and
+ # anything after it) from the request URI.
+ @omniauth_config_full_host = OmniAuth.config.full_host
+ OmniAuth.config.full_host = ->(request) { request['REQUEST_URI'].sub(/#{request['REQUEST_PATH']}.*/, '') }
+ end
+
+ after(:all) do
+ OmniAuth.config.full_host = @omniauth_config_full_host
+ end
+
+ providers.each do |provider|
+ context "when the user logs in using the #{provider} provider" do
+ context 'when two-factor authentication is disabled' do
+ it 'logs the user in' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid')
+
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'logs the user in' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid')
+
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when "remember me" is checked' do
+ context 'when two-factor authentication is disabled' do
+ it 'remembers the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: true)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'remembers the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: true)
+ enter_code(user.current_otp)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq root_path
+ end
+ end
+ end
+
+ context 'when "remember me" is not checked' do
+ context 'when two-factor authentication is disabled' do
+ it 'does not remember the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: false)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'does not remember the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: false)
+ enter_code(user.current_otp)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index b8966cf621c..382d83ca051 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -8,7 +8,7 @@ feature 'Member autocomplete', :js do
before do
note # actually create the note
- gitlab_sign_in(user)
+ sign_in(user)
end
shared_examples "open suggestions when typing @" do
@@ -29,7 +29,7 @@ feature 'Member autocomplete', :js do
context 'adding a new note on a Issue' do
let(:noteable) { create(:issue, author: author, project: project) }
before do
- visit namespace_project_issue_path(project.namespace, project, noteable)
+ visit project_issue_path(project, noteable)
end
include_examples "open suggestions when typing @"
@@ -42,7 +42,7 @@ feature 'Member autocomplete', :js do
target_project: project, author: author)
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, noteable)
+ visit project_merge_request_path(project, noteable)
end
include_examples "open suggestions when typing @"
@@ -56,7 +56,7 @@ feature 'Member autocomplete', :js do
before do
allow_any_instance_of(Commit).to receive(:author).and_return(author)
- visit namespace_project_commit_path(project.namespace, project, noteable)
+ visit project_commit_path(project, noteable)
end
include_examples "open suggestions when typing @"
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index bb4263d83f3..fae11a993b5 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile account page', feature: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'when signup is enabled' do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 33fd29b429b..9d782ecf63b 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > Account', feature: true do
given(:user) { create(:user, username: 'foo') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'Change username' do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index 1a162d6be0e..e4c236f4c68 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -5,7 +5,7 @@ feature 'Profile > Chat', feature: true do
given(:service) { create(:service) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'uses authorization link' do
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index 13f9afd4ce0..9439a258a75 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > SSH Keys', feature: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'User adds a key' do
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index a6f9beafe17..c7886421c83 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Applications', feature: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'User manages applications', js: true do
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 86c9df5ff86..67975a68ee2 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Password', feature: true do
let(:user) { create(:user, password_automatically_set: true) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_profile_password_path
end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index d7acaaf1eb8..44b7ee101c9 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -23,7 +23,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "token creation" do
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 8e7ef6bc110..65fed82c256 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Preferences', feature: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit profile_preferences_path
end
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index c0092836e3b..75daef0c38c 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > Notifications > User changes notified_of_own_activity setting
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'User opts into receiving notifications about their own activity' do
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 84c81d43448..b054f543dc6 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
feature 'Project Activity RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { activity_namespace_project_path(project.namespace, project) }
+ let(:path) { activity_project_path(project) }
before do
create(:issue, project: project)
@@ -10,9 +11,8 @@ feature 'Project Activity RSS' do
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb
index 68375956273..a34c0c4cecd 100644
--- a/spec/features/projects/artifacts/browse_spec.rb
+++ b/spec/features/projects/artifacts/browse_spec.rb
@@ -6,7 +6,7 @@ feature 'Browse artifact', :js, feature: true do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
def browse_path(path)
- browse_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ browse_project_job_artifacts_path(project, job, path)
end
context 'when visiting old URL' do
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
index dd9454840ee..b76f2be880e 100644
--- a/spec/features/projects/artifacts/download_spec.rb
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -22,7 +22,7 @@ feature 'Download artifact', :js, feature: true do
context 'via job id' do
let(:download_url) do
- download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ download_project_job_artifacts_path(project, job)
end
it_behaves_like 'downloading'
@@ -30,7 +30,7 @@ feature 'Download artifact', :js, feature: true do
context 'via branch name and job name' do
let(:download_url) do
- latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
end
it_behaves_like 'downloading'
@@ -44,7 +44,7 @@ feature 'Download artifact', :js, feature: true do
context 'via job id' do
let(:download_url) do
- download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ download_project_job_artifacts_path(project, job)
end
it_behaves_like 'downloading'
@@ -52,7 +52,7 @@ feature 'Download artifact', :js, feature: true do
context 'via branch name and job name' do
let(:download_url) do
- latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
end
it_behaves_like 'downloading'
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 860373e531b..6d48470ca3a 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -10,7 +10,7 @@ feature 'Artifact file', :js, feature: true do
end
def file_path(path)
- file_namespace_project_job_artifacts_path(project.namespace, project, build, path)
+ file_project_job_artifacts_path(project, build, path)
end
context 'Text file' do
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
index b589701729d..3f38d720a0f 100644
--- a/spec/features/projects/artifacts/raw_spec.rb
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -6,7 +6,7 @@ feature 'Raw artifact', :js, feature: true do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
def raw_path(path)
- raw_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ raw_project_job_artifacts_path(project, job, path)
end
context 'when visiting old URL' do
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 9624e1a71b0..5c5a7c96763 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -7,7 +7,7 @@ feature 'test coverage badge' do
context 'when user has access to view badge' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user requests coverage badge image for pipeline' do
@@ -45,7 +45,7 @@ feature 'test coverage badge' do
end
context 'when user does not have access to view badge' do
- background { gitlab_sign_in(user) }
+ background { sign_in(user) }
scenario 'user requests test coverage badge image' do
show_test_coverage_badge
@@ -70,8 +70,7 @@ feature 'test coverage badge' do
end
def show_test_coverage_badge(job: nil)
- visit coverage_namespace_project_badges_path(
- project.namespace, project, ref: :master, job: job, format: :svg)
+ visit coverage_project_badges_path(project, ref: :master, job: job, format: :svg)
end
def expect_coverage_badge(coverage)
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 348748152bb..161d731f524 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -5,8 +5,8 @@ feature 'list of badges' do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_pipelines_settings_path(project.namespace, project)
+ sign_in(user)
+ visit project_pipelines_settings_path(project)
end
scenario 'user wants to see build status badge' do
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index d94204230f6..7564338b301 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -13,14 +13,14 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
end
def visit_blob(fragment = nil)
- visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+ visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'Click "Permalink" button' do
it 'works with no initial line number fragment hash' do
visit_blob
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path))))
end
it 'maintains intitial fragment hash' do
@@ -28,7 +28,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
visit_blob(fragment)
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
@@ -39,7 +39,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -51,7 +51,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
end
@@ -59,7 +59,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
it 'works with no initial line number fragment hash' do
visit_blob
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path))))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path))))
end
it 'maintains intitial fragment hash' do
@@ -67,7 +67,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
visit_blob(fragment)
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
@@ -78,7 +78,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -90,7 +90,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
end
end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 71ffa352f80..3427f639930 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -4,7 +4,7 @@ feature 'File blob', :js, feature: true do
let(:project) { create(:project, :public) }
def visit_blob(path, anchor: nil, ref: 'master')
- visit namespace_project_blob_path(project.namespace, project, File.join(ref, path), anchor: anchor)
+ visit project_blob_path(project, File.join(ref, path), anchor: anchor)
wait_for_requests
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index d0bc032ee93..c9384a09ccd 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -14,7 +14,7 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
def edit_and_commit
@@ -26,7 +26,7 @@ feature 'Editing file blob', feature: true, js: true do
context 'from MR diff' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
edit_and_commit
end
@@ -37,7 +37,7 @@ feature 'Editing file blob', feature: true, js: true do
context 'from blob file path' do
before do
- visit namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_blob_path(project, tree_join(branch, file_path))
edit_and_commit
end
@@ -55,7 +55,7 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, :developer]
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
@@ -63,7 +63,7 @@ feature 'Editing file blob', feature: true, js: true do
gitlab_sign_in(user)
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
end
end
@@ -71,7 +71,7 @@ feature 'Editing file blob', feature: true, js: true do
let(:user) { create(:user) }
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
@@ -79,7 +79,7 @@ feature 'Editing file blob', feature: true, js: true do
gitlab_sign_in(user)
- expect(page).to have_current_path(namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_blob_path(project, tree_join(branch, file_path)))
end
end
end
@@ -92,23 +92,23 @@ feature 'Editing file blob', feature: true, js: true do
project.team << [user, :developer]
project.repository.add_branch(user, protected_branch, 'master')
create(:protected_branch, project: project, name: protected_branch)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'on some branch' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
context 'with protected branch' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(protected_branch, file_path))
+ visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
end
it 'shows blob editor with patch branch' do
@@ -122,12 +122,12 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ sign_in(user)
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index 30e2d587267..9cacda84378 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -12,7 +12,7 @@ feature 'Blob shortcuts', feature: true do
end
def visit_blob(fragment = nil)
- visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+ visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'pressing "y"' do
@@ -21,7 +21,7 @@ feature 'Blob shortcuts', feature: true do
find('body').native.send_key('y')
- expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))), url: true)
+ expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path))), url: true)
end
it 'maintains fragment hash when redirecting' do
@@ -30,7 +30,7 @@ feature 'Blob shortcuts', feature: true do
find('body').native.send_key('y')
- expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)), url: true)
+ expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)), url: true)
end
end
end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index d8c4d475a2c..f01860cc434 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -22,20 +22,18 @@ feature 'Download buttons in branches page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking branches' do
context 'with artifacts' do
before do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, 'binary-encoding/download',
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
index 406fa52e723..8c35dac0b3d 100644
--- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
+++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
@@ -8,8 +8,8 @@ describe 'New Branch Ref Dropdown', :js, :feature do
before do
project.add_master(user)
- gitlab_sign_in(user)
- visit new_namespace_project_branch_path(project.namespace, project)
+ sign_in(user)
+ visit new_project_branch_path(project)
end
it 'filters a list of branches and tags' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 8694366de35..4fae324d8d5 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -19,24 +19,62 @@ describe 'Branches', feature: true do
describe 'Initial branches page' do
it 'shows all the branches' do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
- repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+ repository.branches_sorted_by(:name).first(20).each do |branch|
+ expect(page).to have_content("#{branch.name}")
+ end
expect(page).to have_content("Protected branches can be managed in project settings")
end
+ it 'sorts the branches by name' do
+ visit project_branches_path(project)
+
+ click_button "Name" # Open sorting dropdown
+ click_link "Name"
+
+ sorted = repository.branches_sorted_by(:name).first(20).map do |branch|
+ Regexp.escape(branch.name)
+ end
+ expect(page).to have_content(/#{sorted.join(".*")}/)
+ end
+
+ it 'sorts the branches by last updated' do
+ visit project_branches_path(project)
+
+ click_button "Name" # Open sorting dropdown
+ click_link "Last updated"
+
+ sorted = repository.branches_sorted_by(:updated_desc).first(20).map do |branch|
+ Regexp.escape(branch.name)
+ end
+ expect(page).to have_content(/#{sorted.join(".*")}/)
+ end
+
+ it 'sorts the branches by oldest updated' do
+ visit project_branches_path(project)
+
+ click_button "Name" # Open sorting dropdown
+ click_link "Oldest updated"
+
+ sorted = repository.branches_sorted_by(:updated_asc).first(20).map do |branch|
+ Regexp.escape(branch.name)
+ end
+ expect(page).to have_content(/#{sorted.join(".*")}/)
+ end
+
it 'avoids a N+1 query in branches index' do
- control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_branches_path(project.namespace, project) }.count
+ control_count = ActiveRecord::QueryRecorder.new { visit project_branches_path(project) }.count
%w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
- expect { visit namespace_project_branches_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+ expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count)
end
end
describe 'Find branches' do
it 'shows filtered branches', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -48,7 +86,7 @@ describe 'Branches', feature: true do
describe 'Delete unprotected branch' do
it 'removes branch after confirmation', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
@@ -66,7 +104,7 @@ describe 'Branches', feature: true do
describe 'Delete protected branch' do
before do
project.add_user(user, :master)
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('fix')
click_on "Protect"
@@ -76,7 +114,7 @@ describe 'Branches', feature: true do
end
it 'does not allow devleoper to removes protected branch', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -94,7 +132,7 @@ describe 'Branches', feature: true do
describe 'Delete protected branch' do
before do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('fix')
click_on "Protect"
@@ -103,7 +141,7 @@ describe 'Branches', feature: true do
end
it 'removes branch after modal confirmation', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -126,7 +164,7 @@ describe 'Branches', feature: true do
context 'logged out' do
before do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
end
it 'does not show merge request button' do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index e5b1f95f2b9..257a7418f16 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -6,7 +6,7 @@ feature 'project commit pipelines', js: true do
background do
user = create(:user)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when no builds triggered yet' do
@@ -17,7 +17,7 @@ feature 'project commit pipelines', js: true do
end
scenario 'user views commit pipelines page' do
- visit pipelines_namespace_project_commit_path(project.namespace, project, project.commit.sha)
+ visit pipelines_project_commit_path(project, project.commit.sha)
page.within('.table-holder') do
expect(page).to have_content project.pipelines[0].status # pipeline status
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index 0d3fa72fbf5..2d18add82b5 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -10,7 +10,7 @@ describe 'Cherry-pick Commits' do
before do
sign_in(user)
project.team << [user, :master]
- visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ visit project_commit_path(project, master_pickable_commit.id)
end
context "I cherry-pick a commit" do
@@ -43,7 +43,7 @@ describe 'Cherry-pick Commits' do
uncheck 'create_merge_request'
click_button 'Cherry-pick'
end
- visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ visit project_commit_path(project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request'
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 570a7ae7b16..a5736b6072a 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -5,7 +5,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
let(:project) { create(:project, :public) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when commit has pipelines' do
@@ -22,7 +22,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
before do
build.run
- visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ visit project_commit_path(project, project.commit.id)
end
it 'should display a mini pipeline graph' do
@@ -43,7 +43,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
context 'when commit does not have pipelines' do
before do
- visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ visit project_commit_path(project, project.commit.id)
end
it 'should not display a mini pipeline graph' do
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index f7548a56984..db958346f06 100644
--- a/spec/features/projects/commit/rss_spec.rb
+++ b/spec/features/projects/commit/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project Commits RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_commits_path(project.namespace, project, :master) }
+ let(:path) { project_commits_path(project, :master) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 4743d69fb75..0f48751fa10 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -6,8 +6,8 @@ describe "Compare", js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master")
+ sign_in user
+ visit project_compare_index_path(project, from: "master", to: "master")
end
describe "branches" do
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index a31960639fe..cf3e1ff451e 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -6,7 +6,7 @@ describe 'Project deploy keys', :js, :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'removing key' do
@@ -15,7 +15,7 @@ describe 'Project deploy keys', :js, :feature do
end
it 'removes association between project and deploy key' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
page.within(find('.deploy-keys')) do
expect(page).to have_selector('.deploy-keys li', count: 1)
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index a943f1e6a08..1b0d13e07db 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -7,7 +7,7 @@ feature 'Developer views empty project instructions', feature: true do
background do
project.team << [developer, :developer]
- gitlab_sign_in(developer)
+ sign_in(developer)
end
context 'without an SSH key' do
@@ -47,7 +47,7 @@ feature 'Developer views empty project instructions', feature: true do
end
def visit_project
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
def select_protocol(protocol)
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index 48b7f1e0f34..b528b283495 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -4,7 +4,7 @@ feature 'Diff file viewer', :js, feature: true do
let(:project) { create(:project, :public, :repository) }
def visit_commit(sha, anchor: nil)
- visit namespace_project_commit_path(project.namespace, project, sha, anchor: anchor)
+ visit project_commit_path(project, sha, anchor: anchor)
wait_for_requests
end
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
index ca202b95a44..1fca0dde534 100644
--- a/spec/features/projects/edit_spec.rb
+++ b/spec/features/projects/edit_spec.rb
@@ -6,9 +6,9 @@ feature 'Project edit', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
context 'feature visibility' do
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index b48dcf6c774..cf0dfcfb1f3 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -15,7 +15,7 @@ feature 'Environment > Metrics', :feature do
create(:deployment, environment: environment, deployable: build)
stub_all_prometheus_requests(environment.slug)
- gitlab_sign_in(user)
+ sign_in(user)
visit_environment(environment)
end
@@ -27,13 +27,11 @@ feature 'Environment > Metrics', :feature do
scenario 'shows metrics' do
click_link('See metrics')
- expect(page).to have_css('svg.prometheus-graph')
+ expect(page).to have_css('div#prometheus-graphs')
end
end
def visit_environment(environment)
- visit namespace_project_environment_path(environment.project.namespace,
- environment.project,
- environment)
+ visit project_environment_path(environment.project, environment)
end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 7d565555f1f..c31b816f7fb 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -6,7 +6,7 @@ feature 'Environment', :feature do
given(:role) { :developer }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
@@ -114,7 +114,7 @@ feature 'Environment', :feature do
before do
# Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
allow_any_instance_of(Environment).to receive(:terminals) { nil }
- visit terminal_namespace_project_environment_path(project.namespace, project, environment)
+ visit terminal_project_environment_path(project, environment)
end
it 'displays a web terminal' do
@@ -194,9 +194,7 @@ feature 'Environment', :feature do
name: 'staging-1.0/review',
state: :available)
- visit folder_namespace_project_environments_path(project.namespace,
- project,
- id: 'staging-1.0')
+ visit folder_project_environments_path(project, id: 'staging-1.0')
end
it 'renders a correct environment folder' do
@@ -221,7 +219,7 @@ feature 'Environment', :feature do
end
scenario 'user deletes the branch with running environment' do
- visit namespace_project_branches_path(project.namespace, project, search: 'feature')
+ visit project_branches_path(project, search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
@@ -249,12 +247,10 @@ feature 'Environment', :feature do
end
def visit_environment(environment)
- visit namespace_project_environment_path(environment.project.namespace,
- environment.project,
- environment)
+ visit project_environment_path(environment.project, environment)
end
def have_terminal_button
- have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ have_link(nil, href: terminal_project_environment_path(project, environment))
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index cf4d996a32d..99b917cb420 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -7,7 +7,7 @@ feature 'Environments page', :feature, :js do
background do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
given!(:environment) { }
@@ -29,7 +29,7 @@ feature 'Environments page', :feature, :js do
describe 'in available tab page' do
it 'should show one environment' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ visit project_environments_path(project, scope: 'available')
expect(page).to have_css('.environments-container')
expect(page.all('.environment-name').length).to eq(1)
end
@@ -37,7 +37,7 @@ feature 'Environments page', :feature, :js do
describe 'in stopped tab page' do
it 'should show no environments' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ visit project_environments_path(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
expect(page).to have_content('You don\'t have any environments right now')
end
@@ -49,7 +49,7 @@ feature 'Environments page', :feature, :js do
describe 'in available tab page' do
it 'should show no environments' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ visit project_environments_path(project, scope: 'available')
expect(page).to have_css('.environments-container')
expect(page).to have_content('You don\'t have any environments right now')
end
@@ -57,7 +57,7 @@ feature 'Environments page', :feature, :js do
describe 'in stopped tab page' do
it 'should show one environment' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ visit project_environments_path(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
expect(page.all('.environment-name').length).to eq(1)
end
@@ -277,10 +277,10 @@ feature 'Environments page', :feature, :js do
end
def have_terminal_button
- have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ have_link(nil, href: terminal_project_environment_path(project, environment))
end
def visit_environments(project)
- visit namespace_project_environments_path(project.namespace, project)
+ visit project_environments_path(project)
end
end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index db2790a4bce..827e02a58d0 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -9,7 +9,7 @@ describe 'Edit Project Settings', feature: true do
describe 'project features visibility selectors', js: true do
before do
project.team << [member, :master]
- gitlab_sign_in(member)
+ sign_in(member)
end
tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
@@ -17,7 +17,7 @@ describe 'Edit Project Settings', feature: true do
tools.each do |tool_name, shortcut_name|
describe "feature #{tool_name}" do
it 'toggles visibility' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
@@ -44,7 +44,7 @@ describe 'Edit Project Settings', feature: true do
project.project_feature.update(issues_access_level: ProjectFeature::DISABLED)
allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector(".shortcuts-issues")
end
@@ -52,7 +52,7 @@ describe 'Edit Project Settings', feature: true do
context "pipelines subtabs" do
it "shows builds when enabled" do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
expect(page).to have_selector(".shortcuts-builds")
end
@@ -60,7 +60,7 @@ describe 'Edit Project Settings', feature: true do
it "hides builds when disabled" do
allow(Ability).to receive(:allowed?).with(member, :read_builds, project).and_return(false)
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
expect(page).not_to have_selector(".shortcuts-builds")
end
@@ -73,17 +73,17 @@ describe 'Edit Project Settings', feature: true do
let(:tools) do
{
- builds: namespace_project_job_path(project.namespace, project, job),
- issues: namespace_project_issues_path(project.namespace, project),
- wiki: namespace_project_wiki_path(project.namespace, project, :home),
- snippets: namespace_project_snippets_path(project.namespace, project),
- merge_requests: namespace_project_merge_requests_path(project.namespace, project)
+ builds: project_job_path(project, job),
+ issues: project_issues_path(project),
+ wiki: project_wiki_path(project, :home),
+ snippets: project_snippets_path(project),
+ merge_requests: project_merge_requests_path(project)
}
end
context 'normal user' do
before do
- gitlab_sign_in(member)
+ sign_in(member)
end
it 'renders 200 if tool is enabled' do
@@ -130,7 +130,7 @@ describe 'Edit Project Settings', feature: true do
context 'admin user' do
before do
non_member.update_attribute(:admin, true)
- gitlab_sign_in(non_member)
+ sign_in(non_member)
end
it 'renders 404 if feature is disabled' do
@@ -156,8 +156,8 @@ describe 'Edit Project Settings', feature: true do
describe 'repository visibility', js: true do
before do
project.team << [member, :master]
- gitlab_sign_in(member)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit edit_project_path(project)
end
it "disables repository related features" do
@@ -174,7 +174,7 @@ describe 'Edit Project Settings', feature: true do
click_button "Save changes"
wait_for_requests
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content "Customize your workflow!"
end
@@ -187,7 +187,7 @@ describe 'Edit Project Settings', feature: true do
click_button "Save changes"
wait_for_requests
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
page.within(".event-filter") do
expect(page).to have_selector("a", count: 2)
@@ -205,7 +205,7 @@ describe 'Edit Project Settings', feature: true do
expect(page).to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select "Disabled", from: "project_project_feature_attributes_merge_requests_access_level"
@@ -213,7 +213,7 @@ describe 'Edit Project Settings', feature: true do
expect(page).to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
@@ -221,14 +221,14 @@ describe 'Edit Project Settings', feature: true do
expect(page).not_to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
def save_changes_and_check_activity_tab
click_button "Save changes"
wait_for_requests
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
page.within(".event-filter") do
yield
@@ -242,8 +242,8 @@ describe 'Edit Project Settings', feature: true do
before do
project.team << [member, :guest]
- gitlab_sign_in(member)
- visit namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit project_path(project)
end
it "does not show project statistic for guest" do
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 34aef958ec6..d9a561b23a2 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -6,8 +6,8 @@ feature 'user browses project', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tree_path(project.namespace, project, project.default_branch)
+ sign_in(user)
+ visit project_tree_path(project, project.default_branch)
end
scenario "can see blame of '.gitignore'" do
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
index 2a1cc01fe68..55350db4c34 100644
--- a/spec/features/projects/files/creating_a_file_spec.rb
+++ b/spec/features/projects/files/creating_a_file_spec.rb
@@ -6,8 +6,8 @@ feature 'User wants to create a file', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+ sign_in user
+ visit project_new_blob_path(project, project.default_branch)
end
def submit_new_file(options)
@@ -30,11 +30,6 @@ feature 'User wants to create a file', feature: true do
expect(page).to have_content 'The file has been successfully created'
end
- scenario 'file name contains invalid characters' do
- submit_new_file(file_name: '\\')
- expect(page).to have_content 'Path can contain only'
- end
-
scenario 'file name contains directory traversal' do
submit_new_file(file_name: '../README.md')
expect(page).to have_content 'Path cannot include directory traversal'
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 4f1b8588462..0cd0c9addd0 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -7,9 +7,9 @@ feature 'User wants to add a Dockerfile file', feature: true do
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile')
+ visit project_new_blob_path(project, 'master', file_name: 'Dockerfile')
end
scenario 'user can see Dockerfile dropdown' do
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 60182bfebe9..a2874483149 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -22,21 +22,18 @@ feature 'Download buttons in files tree', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when files tree' do
context 'with artifacts' do
before do
- visit namespace_project_tree_path(
- project.namespace, project, project.default_branch)
+ visit project_tree_path(project, project.default_branch)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{project.default_branch}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
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 6e361ac4312..930e4cf488a 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -5,8 +5,8 @@ feature 'User uses soft wrap whilst editing file', feature: true, js: true do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'test_file-name')
+ 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
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index e97ff5fded7..c295380dfc9 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -17,8 +17,8 @@ feature 'User wants to edit a file', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_edit_blob_path(project.namespace, project,
+ sign_in user
+ visit project_edit_blob_path(project,
File.join(project.default_branch, '.gitignore'))
end
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 83a837fba44..9a1eaee08de 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -6,8 +6,8 @@ feature 'User views files page', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ sign_in user
+ visit project_tree_path(project, project.repository.root_ref)
end
scenario 'user sees folders and submodules sorted together, followed by files' do
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index 6a914820ac9..772f81c8853 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -6,9 +6,9 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
+ visit project_find_file_path(project, project.repository.root_ref)
wait_for_requests
end
diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb
index 166ec5c921b..7a99596585f 100644
--- a/spec/features/projects/files/find_files_spec.rb
+++ b/spec/features/projects/files/find_files_spec.rb
@@ -5,25 +5,18 @@ feature 'Find files button in the tree header', feature: true do
given(:project) { create(:project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
scenario 'project main screen' do
- visit namespace_project_path(
- project.namespace,
- project
- )
+ visit project_path(project)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
scenario 'project tree screen' do
- visit namespace_project_tree_path(
- project.namespace,
- project,
- project.default_branch
- )
+ visit project_tree_path(project, project.default_branch)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 7f02ec6b73d..a3a7b08c013 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -5,8 +5,8 @@ feature 'User wants to add a .gitignore file', feature: true do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ sign_in user
+ visit project_new_blob_path(project, 'master', file_name: '.gitignore')
end
scenario 'user can see .gitignore dropdown' do
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index f4b17c2518c..41afe8014d9 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -5,8 +5,8 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+ sign_in user
+ visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml')
end
scenario 'user can see .gitlab-ci.yml dropdown' do
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 7daf016dd22..57f4a6f1b6f 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -7,12 +7,12 @@ feature 'project owner creates a license file', feature: true, js: true do
project.repository.delete_file(project_master, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
project.team << [project_master, :master]
- gitlab_sign_in(project_master)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project_master)
+ visit project_path(project)
end
scenario 'project master creates a license file manually from a template' do
- visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ visit project_tree_path(project, project.repository.root_ref)
find('.add-to-tree').click
click_link 'New file'
@@ -30,7 +30,7 @@ feature 'project owner creates a license file', feature: true, js: true do
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
@@ -40,7 +40,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(page).to have_content('New file')
expect(current_path).to eq(
- namespace_project_new_blob_path(project.namespace, project, 'master'))
+ project_new_blob_path(project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector')
@@ -54,7 +54,7 @@ feature 'project owner creates a license file', feature: true, js: true do
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index eab19d52030..0604ecb8c8b 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -5,17 +5,17 @@ feature 'project owner sees a link to create a license file in empty project', f
let(:project) { create(:empty_project) }
background do
project.team << [project_master, :master]
- gitlab_sign_in(project_master)
+ sign_in(project_master)
end
scenario 'project master creates a license file from a template' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
expect(page).to have_content('New file')
expect(current_path).to eq(
- namespace_project_new_blob_path(project.namespace, project, 'master'))
+ project_new_blob_path(project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector')
@@ -31,7 +31,7 @@ feature 'project owner sees a link to create a license file in empty project', f
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 028a0919640..a0846643269 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -6,7 +6,7 @@ feature 'Template type dropdown selector', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'editing a non-matching file' do
@@ -31,7 +31,7 @@ feature 'Template type dropdown selector', js: true do
context 'editing a matching file' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, 'LICENSE'))
+ visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
end
scenario 'displayed' do
@@ -61,7 +61,7 @@ feature 'Template type dropdown selector', js: true do
context 'creating a matching file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ visit project_new_blob_path(project, 'master', file_name: '.gitignore')
end
scenario 'is displayed' do
@@ -79,7 +79,7 @@ feature 'Template type dropdown selector', js: true do
context 'creating a file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+ visit project_new_blob_path(project, project.default_branch)
end
scenario 'type selector is shown' do
@@ -129,7 +129,7 @@ def check_type_selector_toggle_text(template_type)
end
def create_and_edit_file(file_name)
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: file_name)
+ visit project_new_blob_path(project, 'master', file_name: file_name)
click_button "Commit changes"
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, file_name))
+ visit project_edit_blob_path(project, File.join(project.default_branch, file_name))
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 4ccd123f46e..d50ddb1f1a9 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -6,12 +6,12 @@ feature 'Template Undo Button', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'editing a matching file and applying a template' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, "LICENSE"))
+ visit project_edit_blob_path(project, File.join(project.default_branch, "LICENSE"))
select_file_template('.js-license-selector', 'Apache License 2.0')
end
@@ -22,7 +22,7 @@ feature 'Template Undo Button', js: true do
context 'creating a non-matching file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, 'master')
+ visit project_new_blob_path(project, 'master')
select_file_template_type('LICENSE')
select_file_template('.js-license-selector', 'Apache License 2.0')
end
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index aa4ed217a34..aa2306069ad 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -4,9 +4,9 @@ describe 'GFM autocomplete loading', feature: true, js: true do
let(:project) { create(:project) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'does not load on project#show' do
@@ -14,7 +14,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do
end
it 'loads on new issue page' do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
expect(evaluate_script('gl.GfmAutoComplete.dataSources')).not_to eq({})
end
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index 778f5d61ae3..631955a60a1 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -9,12 +9,12 @@ feature 'Project group links', :feature, :js do
background do
project.add_master(master)
- gitlab_sign_in(master)
+ sign_in(master)
end
context 'setting an expiration date for a group link' do
before do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
click_on 'share-with-group-tab'
@@ -43,7 +43,7 @@ feature 'Project group links', :feature, :js do
end
it 'does not show ancestors', :nested_groups do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
click_on 'share-with-group-tab'
click_link 'Search for a group'
@@ -61,7 +61,7 @@ feature 'Project group links', :feature, :js do
group.add_owner(master)
group_two.add_owner(master)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
execute_script 'GroupsSelect.PER_PAGE = 1;'
open_select2 '#link_group_id'
end
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index e1f7f06c113..1c5f89fa898 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -7,11 +7,11 @@ describe 'Guest navigation menu' do
before do
project.team << [guest, :guest]
- gitlab_sign_in(guest)
+ sign_in(guest)
end
it 'shows allowed tabs only' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.layout-nav') do
expect(page).to have_content 'Project'
@@ -25,7 +25,7 @@ describe 'Guest navigation menu' do
end
it 'does not show fork button' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.count-buttons') do
expect(page).not_to have_link 'Fork'
@@ -33,7 +33,7 @@ describe 'Guest navigation menu' do
end
it 'does not show clone path' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.project-repo-buttons') do
expect(page).not_to have_selector '.project-clone-holder'
@@ -49,7 +49,7 @@ describe 'Guest navigation menu' do
end
it 'does not show the project file list landing page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_selector '.project-stats'
expect(page).not_to have_selector '.project-last-commit'
@@ -58,7 +58,7 @@ describe 'Guest navigation menu' do
end
it 'shows the customize workflow when issues and wiki are disabled' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.project-show-customize_workflow'
end
@@ -66,7 +66,7 @@ describe 'Guest navigation menu' do
it 'shows the wiki when enabled' do
project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.project-show-wiki'
end
@@ -74,7 +74,7 @@ describe 'Guest navigation menu' do
it 'shows the issues when enabled' do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.issues-list'
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index b5c64777934..43c2c401f4a 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -33,17 +33,17 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
context 'admin user' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'exports a project successfully' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Export project')
click_link 'Export project'
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Download export')
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index a111aa87c52..533ff4612ff 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -53,7 +53,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
select2(namespace.id, from: '#project_namespace_id')
fill_in :project_path, with: project.name, visible: true
click_link 'GitLab export'
-
attach_file('file', file)
click_on 'Import project'
@@ -98,6 +97,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
end
def project_hook_exists?(project)
- Gitlab::Git::Hook.new('post-receive', project.repository.path).exists?
+ Gitlab::Git::Hook.new('post-receive', project).exists?
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 b0a68f0d61f..74ced0d3b35 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -16,7 +16,7 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
context 'admin user' do
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
context 'moving the namespace' do
@@ -48,13 +48,13 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
end
def setup_export_project
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Export project')
click_link 'Export project'
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Download export')
end
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 26a09985312..88bb678362b 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -6,7 +6,7 @@ feature 'issuable templates', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'user creates an issue using templates' do
@@ -28,7 +28,7 @@ feature 'issuable templates', feature: true, js: true do
longtemplate_content,
message: 'added issue template',
branch_name: 'master')
- visit edit_namespace_project_issue_path project.namespace, project, issue
+ visit edit_project_issue_path project, issue
fill_in :'issue[title]', with: 'test issue title'
end
@@ -81,7 +81,7 @@ feature 'issuable templates', feature: true, js: true do
template_content,
message: 'added issue template',
branch_name: 'master')
- visit edit_namespace_project_issue_path project.namespace, project, issue
+ visit edit_project_issue_path project, issue
fill_in :'issue[title]', with: 'test issue title'
fill_in :'issue[description]', with: prior_description
end
@@ -105,7 +105,7 @@ feature 'issuable templates', feature: true, js: true do
template_content,
message: 'added merge request template',
branch_name: 'master')
- visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+ visit edit_project_merge_request_path project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
@@ -124,18 +124,21 @@ feature 'issuable templates', feature: true, js: true do
let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
background do
- gitlab_sign_out
+ sign_out(:user)
+
project.team << [fork_user, :developer]
fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
- gitlab_sign_in fork_user
+
+ sign_in(fork_user)
+
project.repository.create_file(
fork_user,
'.gitlab/merge_request_templates/feature-proposal.md',
template_content,
message: 'added merge request template',
branch_name: 'master')
- visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+ visit edit_project_merge_request_path project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb
index b2db07a75ef..c2ca62508a4 100644
--- a/spec/features/projects/issues/list_spec.rb
+++ b/spec/features/projects/issues/list_spec.rb
@@ -7,13 +7,13 @@ feature 'Issues List' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user does not see create new list button' do
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).not_to have_selector('.js-new-board-list')
end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index 38733d39932..d274a1760a4 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -2,17 +2,18 @@ require 'spec_helper'
feature 'Project Issues RSS' do
let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_issues_path(project.namespace, project) }
+ let(:path) { project_issues_path(project) }
before do
create(:issue, project: project)
end
context 'when signed in' do
+ let(:user) { create(:user) }
+
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 070cdbf1cef..411987573fa 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -5,7 +5,6 @@ feature 'Jobs', :feature do
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project) }
- let(:namespace) { project.namespace }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
@@ -17,7 +16,7 @@ feature 'Jobs', :feature do
before do
project.team << [user, user_access_level]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "GET /:project/jobs" do
@@ -25,7 +24,7 @@ feature 'Jobs', :feature do
context "Pending scope" do
before do
- visit namespace_project_jobs_path(project.namespace, project, scope: :pending)
+ visit project_jobs_path(project, scope: :pending)
end
it "shows Pending tab jobs" do
@@ -40,7 +39,7 @@ feature 'Jobs', :feature do
context "Running scope" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project, scope: :running)
+ visit project_jobs_path(project, scope: :running)
end
it "shows Running tab jobs" do
@@ -55,7 +54,7 @@ feature 'Jobs', :feature do
context "Finished scope" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project, scope: :finished)
+ visit project_jobs_path(project, scope: :finished)
end
it "shows Finished tab jobs" do
@@ -68,7 +67,7 @@ feature 'Jobs', :feature do
context "All jobs" do
before do
project.builds.running_or_pending.each(&:success)
- visit namespace_project_jobs_path(project.namespace, project)
+ visit project_jobs_path(project)
end
it "shows All tab jobs" do
@@ -82,7 +81,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:jobs_url) do
- namespace_project_jobs_path(project.namespace, project)
+ project_jobs_path(project)
end
before do
@@ -98,7 +97,7 @@ feature 'Jobs', :feature do
describe "POST /:project/jobs/:id/cancel_all" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project)
+ visit project_jobs_path(project)
click_link "Cancel running"
end
@@ -117,7 +116,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows status name', :js do
@@ -140,7 +139,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows retry button' do
@@ -157,7 +156,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
before do
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows New issue button' do
@@ -166,10 +165,10 @@ feature 'Jobs', :feature do
it 'links to issues/new with the title and description filled in' do
button_title = "Build Failed ##{job.id}"
- job_path = namespace_project_job_path(namespace, project, job)
+ job_path = project_job_path(project, job)
options = { issue: { title: button_title, description: job_path } }
- href = new_namespace_project_issue_path(namespace, project, options)
+ href = new_project_issue_path(project, options)
page.within('.header-action-buttons') do
expect(find('.js-new-issue')['href']).to include(href)
@@ -180,7 +179,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit namespace_project_job_path(project.namespace, project, job2)
+ visit project_job_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
@@ -189,7 +188,7 @@ feature 'Jobs', :feature do
context "Download artifacts" do
before do
job.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'has button to download artifacts' do
@@ -202,7 +201,7 @@ feature 'Jobs', :feature do
job.update_attributes(artifacts_file: artifacts_file,
artifacts_expire_at: expire_at)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
context 'no expire date defined' do
@@ -248,7 +247,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:job_url) do
- namespace_project_job_path(project.namespace, project, job)
+ project_job_path(project, job)
end
before do
@@ -264,7 +263,7 @@ feature 'Jobs', :feature do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it do
@@ -276,7 +275,7 @@ feature 'Jobs', :feature do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
context 'when job has an initial trace' do
@@ -300,7 +299,7 @@ feature 'Jobs', :feature do
end
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows variable key and value after click', js: true do
@@ -325,7 +324,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link environment.name
end
@@ -335,7 +334,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link environment.name
end
@@ -346,7 +345,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link('latest deployment')
end
@@ -358,7 +357,7 @@ feature 'Jobs', :feature do
context "Job from project" do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-cancel-job').click()
end
@@ -373,7 +372,7 @@ feature 'Jobs', :feature do
context "Job from project", :js do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-cancel-job').click()
find('.js-retry-button').trigger('click')
end
@@ -392,9 +391,9 @@ feature 'Jobs', :feature do
job.cancel!
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- gitlab_sign_out_direct
- gitlab_sign_in(create(:user))
- visit namespace_project_job_path(project.namespace, project, job)
+ sign_out(:user)
+ sign_in(create(:user))
+ visit project_job_path(project, job)
end
it 'does not show the Retry button' do
@@ -408,14 +407,14 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/download" do
before do
job.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
click_link 'Download'
end
context "Build from other project" do
before do
job2.update_attributes(artifacts_file: artifacts_file)
- visit download_namespace_project_job_artifacts_path(project.namespace, project, job2)
+ visit download_project_job_artifacts_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
@@ -428,7 +427,7 @@ feature 'Jobs', :feature do
before do
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-raw-link-controller').click()
end
@@ -443,7 +442,7 @@ feature 'Jobs', :feature do
before do
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job2.run!
- visit raw_namespace_project_job_path(project.namespace, project, job2)
+ visit raw_project_job_path(project, job2)
end
it 'sends the right headers' do
@@ -467,7 +466,7 @@ feature 'Jobs', :feature do
.to receive(:paths)
.and_return([existing_file])
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
find('.js-raw-link-controller').click
end
@@ -485,7 +484,7 @@ feature 'Jobs', :feature do
.to receive(:paths)
.and_return([])
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
end
it 'sends the right headers' do
@@ -496,7 +495,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:raw_job_url) do
- raw_namespace_project_job_path(project.namespace, project, job)
+ raw_project_job_path(project, job)
end
before do
@@ -512,7 +511,7 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/trace.json" do
context "Job from project" do
before do
- visit trace_namespace_project_job_path(project.namespace, project, job, format: :json)
+ visit trace_project_job_path(project, job, format: :json)
end
it { expect(page.status_code).to eq(200) }
@@ -520,7 +519,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit trace_namespace_project_job_path(project.namespace, project, job2, format: :json)
+ visit trace_project_job_path(project, job2, format: :json)
end
it { expect(page.status_code).to eq(404) }
@@ -530,7 +529,7 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/status" do
context "Job from project" do
before do
- visit status_namespace_project_job_path(project.namespace, project, job)
+ visit status_project_job_path(project, job)
end
it { expect(page.status_code).to eq(200) }
@@ -538,7 +537,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit status_namespace_project_job_path(project.namespace, project, job2)
+ visit status_project_job_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 2c47758f30e..652008bae73 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -28,8 +28,8 @@ feature 'Issue prioritization', feature: true do
issue_2.labels << label_4
issue_1.labels << label_5
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+ sign_in user
+ visit project_issues_path(project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
@@ -67,8 +67,8 @@ feature 'Issue prioritization', feature: true do
issue_4.labels << label_4 # 7
issue_6.labels << label_5 # 8 - No priority
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+ sign_in user
+ visit project_issues_path(project, sort: 'label_priority')
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 584dc294f05..58421e11e0a 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -10,11 +10,11 @@ feature 'Labels subscription', feature: true do
context 'when signed in' do
before do
project.team << [user, :developer]
- gitlab_sign_in user
+ sign_in user
end
scenario 'users can subscribe/unsubscribe to labels', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('bug')
expect(page).to have_content('feature')
@@ -55,7 +55,7 @@ feature 'Labels subscription', feature: true do
context 'when not signed in' do
it 'users can not subscribe/unsubscribe to labels' do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 589bfb9fbc9..61f6d734ed3 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -14,11 +14,11 @@ feature 'Prioritize labels', feature: true do
before do
project.team << [user, :developer]
- gitlab_sign_in user
+ sign_in user
end
scenario 'user can prioritize a group label', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('Star labels to start sorting by priority')
@@ -37,7 +37,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can unprioritize a group label', js: true do
create(:label_priority, project: project, label: feature, priority: 1)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
page.within('.prioritized-labels') do
expect(page).to have_content('feature')
@@ -53,7 +53,7 @@ feature 'Prioritize labels', feature: true do
end
scenario 'user can prioritize a project label', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('Star labels to start sorting by priority')
@@ -72,7 +72,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can unprioritize a project label', js: true do
create(:label_priority, project: project, label: bug, priority: 1)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
page.within('.prioritized-labels') do
expect(page).to have_content('bug')
@@ -92,7 +92,7 @@ feature 'Prioritize labels', feature: true do
create(:label_priority, project: project, label: bug, priority: 1)
create(:label_priority, project: project, label: feature, priority: 2)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
@@ -120,9 +120,9 @@ feature 'Prioritize labels', feature: true do
it 'does not prioritize labels' do
guest = create(:user)
- gitlab_sign_in guest
+ sign_in guest
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
@@ -133,7 +133,7 @@ feature 'Prioritize labels', feature: true do
context 'as a non signed in user' do
it 'does not prioritize labels' do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index 514453db472..8b952d2f3a5 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -22,20 +22,18 @@ feature 'Download buttons in project main page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking project main page' do
context 'with artifacts' do
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{project.default_branch}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index fee8cfe2c33..7914180b951 100644
--- a/spec/features/projects/main/rss_spec.rb
+++ b/spec/features/projects/main/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_path(project.namespace, project) }
+ let(:path) { project_path(project) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index d82cf53c690..28c8d20aad5 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -11,10 +11,10 @@ feature 'Projects > Members > Anonymous user sees members', feature: true do
end
scenario "anonymous user visits the project's members page and sees the list of members" do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect(current_path).to eq(
- namespace_project_settings_members_path(project.namespace, project))
+ project_project_members_path(project))
expect(page).to have_content(user.name)
end
end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index 00d2a27597b..b9154915b34 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -9,8 +9,8 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
project.team << [user, :master]
@group_link = create(:project_group_link, project: project, group: group)
- gitlab_sign_in(user)
- visit namespace_project_settings_members_path(project.namespace, project)
+ sign_in(user)
+ visit project_settings_members_path(project)
end
it 'updates group access level' do
@@ -22,7 +22,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
wait_for_requests
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
expect(first('.group_member')).to have_content('Guest')
end
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index 7e71dbc24c0..2c99c2c7888 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -7,8 +7,8 @@ feature 'Projects > Members > Group member cannot leave group project', feature:
background do
group.add_developer(user)
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index 60a5cd9ec63..35142273eae 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -41,7 +41,7 @@ feature 'Projects > Members > Group member cannot request access to his group pr
end
def login_and_visit_project_page(user)
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
end
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 76fe6a00dab..bfc604bb8d6 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -13,13 +13,13 @@ feature 'Projects members', feature: true do
background do
project.team << [developer, :developer]
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'with a group invitee' do
before do
group_invitee
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'does not appear in the project members page' do
@@ -33,7 +33,7 @@ feature 'Projects members', feature: true do
before do
group_invitee
project_invitee
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'shows the project invitee, the project developer, and the group owner' do
@@ -54,7 +54,7 @@ feature 'Projects members', feature: true do
context 'with a group requester' do
before do
group.request_access(group_requester)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'does not appear in the project members page' do
@@ -68,7 +68,7 @@ feature 'Projects members', feature: true do
before do
group.request_access(group_requester)
project.request_access(project_requester)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'shows the project requester, the project developer, and the group owner' do
diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
index 66da28b07fe..46f5744b32d 100644
--- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
+++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
@@ -8,10 +8,10 @@ feature 'Projects > Members > Group requester cannot request access to project',
background do
group.add_owner(owner)
- gitlab_sign_in(user)
+ sign_in(user)
visit group_path(group)
perform_enqueued_jobs { click_link 'Request Access' }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
scenario 'group requester does not see the request access / withdraw access request button' do
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index 9fdd7df0ee5..301f68a67d3 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -9,7 +9,7 @@ feature 'Project members list', feature: true do
let(:project) { create(:project, namespace: group) }
background do
- gitlab_sign_in(user1)
+ sign_in(user1)
group.add_owner(user1)
end
@@ -85,6 +85,6 @@ feature 'Project members list', feature: true do
end
def visit_members_page
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 21b48b7fdd1..14edfb6e673 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
@@ -10,13 +10,13 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
background do
project.team << [master, :master]
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'expiration date is displayed in the members list' do
travel_to Time.zone.parse('2016-08-06 08:00') do
date = 4.days.from_now
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
@@ -34,7 +34,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
travel_to Time.zone.parse('2016-08-06 08:00') do
date = 3.days.from_now
project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium))
- visit namespace_project_project_members_path(project.namespace, project)
+ 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)
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index bd445e27243..a359c209556 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -8,17 +8,17 @@ feature 'Projects > Members > Master manages access requests', feature: true do
background do
project.request_access(user)
project.team << [master, :master]
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'master can see access requests' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
end
scenario 'master can grant access' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
@@ -29,7 +29,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
end
scenario 'master can deny access' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
index 703f5dff6b5..55852012bae 100644
--- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -6,8 +6,8 @@ feature 'Projects > Members > Member cannot request access to his project', feat
background do
project.team << [member, :developer]
- gitlab_sign_in(member)
- visit namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit project_path(project)
end
scenario 'member does not see the request access button' do
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index 8e1788f7f2a..3de13aee0ee 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -6,8 +6,8 @@ feature 'Projects > Members > Member leaves project', feature: true do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'user leaves project' do
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
index 70e4bb19c0f..fae52325be0 100644
--- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -4,8 +4,8 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project.owner)
+ visit project_path(project)
end
scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
index 0cd7e3afeda..a7a5e01465f 100644
--- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -4,8 +4,8 @@ feature 'Projects > Members > Owner cannot request access to his project', featu
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project.owner)
+ visit project_path(project)
end
scenario 'owner does not see the request access button' do
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index 66d98ef8b90..afb613f034e 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -8,7 +8,7 @@ feature 'Projects > Members > Sorting', feature: true do
background do
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'sorts alphabetically by default' do
@@ -84,7 +84,7 @@ feature 'Projects > Members > Sorting', feature: true do
end
def visit_members_list(sort:)
- visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort)
+ visit project_project_members_path(project, sort: sort)
end
def first_member
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 081009f2325..ab86e2da4f6 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -6,13 +6,13 @@ feature 'Projects > Members > User requests access', feature: true do
let(:master) { project.owner }
background do
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'request access feature is disabled' do
project.update_attributes(request_access_enabled: false)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_content 'Request Access'
end
@@ -35,7 +35,7 @@ feature 'Projects > Members > User requests access', feature: true do
project.project_feature.update!(repository_access_level: ProjectFeature::PRIVATE,
builds_access_level: ProjectFeature::PRIVATE,
merge_requests_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content 'Request Access'
end
@@ -46,10 +46,11 @@ feature 'Projects > Members > User requests access', feature: true do
expect(project.requesters.exists?(user_id: user)).to be_truthy
- open_project_settings_menu
- click_link 'Members'
+ page.within('.layout-nav .nav-links') do
+ click_link('Members')
+ end
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within('.content') do
expect(page).not_to have_content(user.name)
end
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 6de8855016d..12b4747602d 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -18,15 +18,14 @@ feature 'Merge Request button', feature: true do
context 'logged in as developer' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
it 'shows Create merge request button' do
- href = new_namespace_project_merge_request_path(project.namespace,
- project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ href = project_new_merge_request_path(project,
+ merge_request: { source_branch: 'feature',
+ target_branch: 'master' })
visit url
@@ -52,7 +51,7 @@ feature 'Merge Request button', feature: true do
context 'logged in as non-member' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'does not show Create merge request button' do
@@ -67,10 +66,9 @@ feature 'Merge Request button', feature: true do
let(:user) { forked_project.owner }
it 'shows Create merge request button' do
- href = new_namespace_project_merge_request_path(forked_project.namespace,
- forked_project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ href = project_new_merge_request_path(forked_project,
+ merge_request: { source_branch: 'feature',
+ target_branch: 'master' })
visit fork_url
@@ -85,24 +83,24 @@ feature 'Merge Request button', feature: true do
context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' }
- let(:url) { namespace_project_branches_path(project.namespace, project, search: 'feature') }
- let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project, search: 'feature') }
+ let(:url) { project_branches_path(project, search: 'feature') }
+ let(:fork_url) { project_branches_path(forked_project, search: 'feature') }
end
end
context 'on compare page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Create merge request' }
- let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') }
- let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') }
+ let(:url) { project_compare_path(project, from: 'master', to: 'feature') }
+ let(:fork_url) { project_compare_path(forked_project, from: 'master', to: 'feature') }
end
end
context 'on commits page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Create merge request' }
- let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') }
- let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') }
+ let(:url) { project_commits_path(project, 'feature') }
+ let(:fork_url) { project_commits_path(forked_project, 'feature') }
end
end
end
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
index f2a2fd0311f..6548b4b83e6 100644
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ b/spec/features/projects/merge_requests/list_spec.rb
@@ -7,34 +7,34 @@ feature 'Merge Requests List' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user does not see create new list button' do
create(:merge_request, source_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).not_to have_selector('.js-new-board-list')
end
it 'should show an empty state' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_selector('.empty-state')
end
it 'empty state should have a create merge request button' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
- expect(page).to have_link 'New merge request', href: new_namespace_project_merge_request_path(project.namespace, project)
+ expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
end
context 'if there are merge requests' do
before do
create(:merge_request, assignee: user, source_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'should not show an empty state' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index a02e4118784..642ca7448a3 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -6,12 +6,12 @@ feature 'Project milestone', :feature do
let(:milestone) { create(:milestone, project: project) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when project has enabled issues' do
before do
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'shows issues tab' do
@@ -38,7 +38,7 @@ feature 'Project milestone', :feature do
context 'when project has disabled issues' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'hides issues tab' do
@@ -68,7 +68,7 @@ feature 'Project milestone', :feature do
before do
create(:issue, project: project, milestone: milestone)
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
describe 'the collapsed sidebar' do
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index 2350089255d..53cd2711666 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -15,11 +15,11 @@ feature 'Milestones sorting', :feature, :js do
due_date: 11.days.from_now,
created_at: 1.hour.ago,
title: "bbb", project: project)
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'visit project milestones and sort by due_date_asc' do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
expect(page).to have_button('Due soon')
diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb
index 7403822c7fb..3c81db502bc 100644
--- a/spec/features/projects/milestones/new_spec.rb
+++ b/spec/features/projects/milestones/new_spec.rb
@@ -6,7 +6,7 @@ feature 'Creating a new project milestone', :feature, :js do
before do
login_as(user)
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
end
it 'description has autocomplete' do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 37d9a97033b..22fb1223739 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,13 +1,27 @@
-require "spec_helper"
+require 'spec_helper'
-feature "New project", feature: true do
+feature 'New project' do
let(:user) { create(:admin) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
- context "Visibility level selector" do
+ it 'shows "New project" page' do
+ visit new_project_path
+
+ expect(page).to have_content('Project path')
+ expect(page).to have_content('Project name')
+
+ expect(page).to have_link('GitHub')
+ expect(page).to have_link('Bitbucket')
+ expect(page).to have_link('GitLab.com')
+ expect(page).to have_link('Google Code')
+ expect(page).to have_button('Repo by URL')
+ expect(page).to have_link('GitLab export')
+ end
+
+ context 'Visibility level selector' do
Gitlab::VisibilityLevel.options.each do |key, level|
it "sets selector to #{key}" do
stub_application_setting(default_project_visibility: level)
@@ -28,20 +42,20 @@ feature "New project", feature: true do
end
end
- context "Namespace selector" do
- context "with user namespace" do
+ context 'Namespace selector' do
+ context 'with user namespace' do
before do
visit new_project_path
end
- it "selects the user namespace" do
- namespace = find("#project_namespace_id")
+ it 'selects the user namespace' do
+ namespace = find('#project_namespace_id')
expect(namespace.text).to eq user.username
end
end
- context "with group namespace" do
+ context 'with group namespace' do
let(:group) { create(:group, :private, owner: user) }
before do
@@ -49,13 +63,13 @@ feature "New project", feature: true do
visit new_project_path(namespace_id: group.id)
end
- it "selects the group namespace" do
- namespace = find("#project_namespace_id option[selected]")
+ it 'selects the group namespace' do
+ namespace = find('#project_namespace_id option[selected]')
expect(namespace.text).to eq group.name
end
- context "on validation error" do
+ context 'on validation error' do
before do
fill_in('project_path', with: 'private-group-project')
choose('Internal')
@@ -64,15 +78,15 @@ feature "New project", feature: true do
expect(page).to have_css '.project-edit-errors .alert.alert-danger'
end
- it "selects the group namespace" do
- namespace = find("#project_namespace_id option[selected]")
+ it 'selects the group namespace' do
+ namespace = find('#project_namespace_id option[selected]')
expect(namespace.text).to eq group.name
end
end
end
- context "with subgroup namespace" do
+ context 'with subgroup namespace' do
let(:group) { create(:group, :private, owner: user) }
let(:subgroup) { create(:group, parent: group) }
@@ -81,8 +95,8 @@ feature "New project", feature: true do
visit new_project_path(namespace_id: subgroup.id)
end
- it "selects the group namespace" do
- namespace = find("#project_namespace_id option[selected]")
+ it 'selects the group namespace' do
+ namespace = find('#project_namespace_id option[selected]')
expect(namespace.text).to eq subgroup.full_path
end
@@ -94,10 +108,45 @@ feature "New project", feature: true do
visit new_project_path
end
- it 'does not autocomplete sensitive git repo URL' do
- autocomplete = find('#project_import_url')['autocomplete']
+ context 'from git repository url' do
+ before do
+ first('.import_git').click
+ end
+
+ it 'does not autocomplete sensitive git repo URL' do
+ autocomplete = find('#project_import_url')['autocomplete']
+
+ expect(autocomplete).to eq('off')
+ end
+
+ it 'shows import instructions' do
+ git_import_instructions = first('.js-toggle-content')
- expect(autocomplete).to eq('off')
+ expect(git_import_instructions).to be_visible
+ expect(git_import_instructions).to have_content 'Git repository URL'
+ end
+ end
+
+ context 'from GitHub' do
+ before do
+ first('.import_github').click
+ end
+
+ it 'shows import instructions' do
+ expect(page).to have_content('Import Projects from GitHub')
+ expect(current_path).to eq new_import_github_path
+ end
+ end
+
+ context 'from Google Code' do
+ before do
+ first('.import_google_code').click
+ end
+
+ it 'shows import instructions' do
+ expect(page).to have_content('Import projects from Google Code')
+ expect(current_path).to eq new_import_google_code_path
+ end
end
end
end
diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb
index 30a16e38e3c..53ac18fa7cc 100644
--- a/spec/features/projects/no_password_spec.rb
+++ b/spec/features/projects/no_password_spec.rb
@@ -6,7 +6,7 @@ feature 'No Password Alert' do
context 'with internal auth enabled' do
before do
sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
context 'when user has a password' do
@@ -37,7 +37,7 @@ feature 'No Password Alert' do
context 'when user has no personal access tokens' do
it 'has a personal access token alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
@@ -47,7 +47,7 @@ feature 'No Password Alert' do
it 'shows no alert' do
create(:personal_access_token, user: user)
gitlab_sign_in_via('saml', user, 'my-uid')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
@@ -59,7 +59,7 @@ feature 'No Password Alert' do
before do
sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows no alert' do
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index e9a3cfb7f60..a8593709f1b 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -10,12 +10,12 @@ feature 'Pages', feature: true do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
shared_examples 'no pages deployed' do
scenario 'does not see anything to destroy' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).not_to have_link('Remove pages')
expect(page).not_to have_text('Only the project owner can remove pages')
@@ -33,7 +33,7 @@ feature 'Pages', feature: true do
end
scenario 'sees "Remove pages" link' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).to have_link('Remove pages')
end
@@ -49,7 +49,7 @@ feature 'Pages', feature: true do
end
scenario 'sees "Only the project owner can remove pages" text' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).to have_text('Only the project owner can remove pages')
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index dfb973c37e5..992a68b25a5 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Pipeline Schedules', :feature do
+feature 'Pipeline Schedules', :feature, js: true do
include PipelineSchedulesHelper
let!(:project) { create(:project) }
@@ -11,27 +11,20 @@ feature 'Pipeline Schedules', :feature do
before do
project.add_master(user)
-
- gitlab_sign_in(user)
- visit_page
+ sign_in(user)
end
describe 'GET /projects/pipeline_schedules' do
- let(:visit_page) { visit_pipelines_schedules }
-
- it 'avoids N + 1 queries' do
- control_count = ActiveRecord::QueryRecorder.new { visit_pipelines_schedules }.count
-
- create_list(:ci_pipeline_schedule, 2, project: project)
-
- expect { visit_pipelines_schedules }.not_to exceed_query_limit(control_count)
+ before do
+ visit_pipelines_schedules
end
describe 'The view' do
it 'displays the required information description' do
page.within('.pipeline-schedule-table-row') do
expect(page).to have_content('pipeline schedule')
- expect(page).to have_content(pipeline_schedule.real_next_run.strftime('%b %d, %Y'))
+ expect(find(".next-run-cell time")['data-original-title'])
+ .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y'))
expect(page).to have_link('master')
expect(page).to have_link("##{pipeline.id}")
end
@@ -62,7 +55,7 @@ feature 'Pipeline Schedules', :feature do
it 'deletes the pipeline' do
click_link 'Delete'
- expect(page).not_to have_content('pipeline schedule')
+ expect(page).not_to have_css(".pipeline-schedule-table-row")
end
end
@@ -78,8 +71,10 @@ feature 'Pipeline Schedules', :feature do
end
end
- describe 'POST /projects/pipeline_schedules/new', js: true do
- let(:visit_page) { visit_new_pipeline_schedule }
+ describe 'POST /projects/pipeline_schedules/new' do
+ before do
+ visit_new_pipeline_schedule
+ end
it 'sets defaults for timezone and target branch' do
expect(page).to have_button('master')
@@ -100,8 +95,8 @@ feature 'Pipeline Schedules', :feature do
end
end
- describe 'PATCH /projects/pipelines_schedules/:id/edit', js: true do
- let(:visit_page) do
+ describe 'PATCH /projects/pipelines_schedules/:id/edit' do
+ before do
edit_pipeline_schedule
end
@@ -134,16 +129,82 @@ feature 'Pipeline Schedules', :feature do
end
end
+ context 'when user creates a new pipeline schedule with variables' do
+ background do
+ visit_pipelines_schedules
+ click_link 'New schedule'
+ fill_in_schedule_form
+ all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA')
+ all('[name="schedule[variables_attributes][][value]"]')[0].set('AAA123')
+ all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB')
+ all('[name="schedule[variables_attributes][][value]"]')[1].set('BBB123')
+ save_pipeline_schedule
+ end
+
+ scenario 'user sees the new variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123')
+ expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB')
+ expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123')
+ end
+ end
+ end
+
+ context 'when user edits a variable of a pipeline schedule' do
+ background do
+ create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
+ create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule)
+ end
+
+ visit_pipelines_schedules
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ all('[name="schedule[variables_attributes][][key]"]')[0].set('foo')
+ all('[name="schedule[variables_attributes][][value]"]')[0].set('bar')
+ click_button 'Save pipeline schedule'
+ end
+
+ scenario 'user sees the updated variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar')
+ end
+ end
+ end
+
+ context 'when user removes a variable of a pipeline schedule' do
+ background do
+ create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
+ create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule)
+ end
+
+ visit_pipelines_schedules
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ find('.pipeline-variable-list .pipeline-variable-row-remove-button').click
+ click_button 'Save pipeline schedule'
+ end
+
+ scenario 'user does not see the removed variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('')
+ end
+ end
+ end
+
def visit_new_pipeline_schedule
- visit new_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+ visit new_project_pipeline_schedule_path(project, pipeline_schedule)
end
def edit_pipeline_schedule
- visit edit_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+ visit edit_project_pipeline_schedule_path(project, pipeline_schedule)
end
def visit_pipelines_schedules
- visit namespace_project_pipeline_schedules_path(project.namespace, project, scope: scope)
+ visit project_pipeline_schedules_path(project, scope: scope)
end
def select_timezone
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index e182995922d..4a08d9088aa 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,13 +1,11 @@
require 'spec_helper'
describe 'Pipeline', :feature, :js do
- include GitlabRoutingHelper
-
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
@@ -48,7 +46,7 @@ describe 'Pipeline', :feature, :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
before do
- visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit project_pipeline_path(project, pipeline)
end
it 'shows the pipeline graph' do
@@ -194,7 +192,7 @@ describe 'Pipeline', :feature, :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
- visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit builds_project_pipeline_path(project, pipeline)
end
it 'shows a list of jobs' do
@@ -266,7 +264,7 @@ describe 'Pipeline', :feature, :js do
describe 'GET /:project/pipelines/:id/failures' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
- let(:pipeline_failures_page) { failures_namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
context 'with failed build' do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index d36d073e022..d776fbc2b12 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -7,7 +7,7 @@ describe 'Pipelines', :feature, :js do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
@@ -51,7 +51,7 @@ describe 'Pipelines', :feature, :js do
context 'header tabs' do
before do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
wait_for_requests
end
@@ -369,14 +369,14 @@ describe 'Pipelines', :feature, :js do
end
it 'should render pagination' do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
wait_for_requests
expect(page).to have_selector('.gl-pagination')
end
it 'should render second page of pipelines' do
- visit namespace_project_pipelines_path(project.namespace, project, page: '2')
+ visit project_pipelines_path(project, page: '2')
wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2)
@@ -405,7 +405,7 @@ describe 'Pipelines', :feature, :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
- visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit project_pipeline_path(project, pipeline)
wait_for_requests
end
@@ -440,7 +440,7 @@ describe 'Pipelines', :feature, :js do
let(:project) { create(:project) }
before do
- visit new_namespace_project_pipeline_path(project.namespace, project)
+ visit new_project_pipeline_path(project)
end
context 'for valid commit', js: true do
@@ -479,7 +479,7 @@ describe 'Pipelines', :feature, :js do
let(:project) { create(:project) }
before do
- visit new_namespace_project_pipeline_path(project.namespace, project)
+ visit new_project_pipeline_path(project)
end
describe 'new pipeline page' do
@@ -508,7 +508,7 @@ describe 'Pipelines', :feature, :js do
context 'when user is not logged in' do
before do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
end
context 'when project is public' do
@@ -526,7 +526,7 @@ describe 'Pipelines', :feature, :js do
end
def visit_project_pipelines(**query)
- visit namespace_project_pipelines_path(project.namespace, project, query)
+ visit project_pipelines_path(project, query)
wait_for_requests
end
end
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index baa38ff8cca..89d227eb98f 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -7,12 +7,12 @@ describe 'Edit Project Settings', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace, path: 'gitlab', name: 'sample') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'Project settings section', js: true do
it 'shows errors for invalid project name' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in 'project_name_edit', with: 'foo&bar'
click_button 'Save changes'
expect(page).to have_field 'project_name_edit', with: 'foo&bar'
@@ -21,7 +21,7 @@ describe 'Edit Project Settings', feature: true do
end
it 'shows a successful notice when the project is updated' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in 'project_name_edit', with: 'hello world'
click_button 'Save changes'
expect(page).to have_content "Project 'hello world' was successfully updated."
@@ -75,7 +75,7 @@ describe 'Edit Project Settings', feature: true do
end
specify 'the project is accessible via a redirect from the old path' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
rename_project(project, path: 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
@@ -85,7 +85,7 @@ describe 'Edit Project Settings', feature: true do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
rename_project(project, path: 'bar')
new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
@@ -122,7 +122,7 @@ describe 'Edit Project Settings', feature: true do
end
specify 'the project is accessible via a redirect from the old path' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
transfer_project(project, group)
new_path = namespace_project_path(group, project)
visit old_path
@@ -132,7 +132,7 @@ describe 'Edit Project Settings', feature: true do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
transfer_project(project, group)
new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
@@ -144,7 +144,7 @@ describe 'Edit Project Settings', feature: true do
end
def rename_project(project, name: nil, path: nil)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in('project_name', with: name) if name
fill_in('Path', with: path) if path
click_button('Rename project')
@@ -153,7 +153,7 @@ def rename_project(project, name: nil, path: nil)
end
def transfer_project(project, namespace)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select2(namespace.id, from: '#new_namespace_id')
click_button('Transfer project')
confirm_transfer_modal
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 016a992bdcf..31c7b492ab7 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -6,8 +6,8 @@ feature 'Ref switcher', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tree_path(project.namespace, project, 'master')
+ sign_in(user)
+ visit project_tree_path(project, 'master')
end
it 'allow user to change ref by enter key' do
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
index 8cd216c8fdb..7c29af247d6 100644
--- a/spec/features/projects/services/jira_service_spec.rb
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -24,9 +24,9 @@ feature 'Setup Jira service', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_settings_integrations_path(project.namespace, project)
+ visit project_settings_integrations_path(project)
end
describe 'user sets and activates Jira Service' do
@@ -42,7 +42,7 @@ feature 'Setup Jira service', :feature, :js do
wait_for_requests
expect(page).to have_content('JIRA activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
@@ -76,7 +76,7 @@ feature 'Setup Jira service', :feature, :js do
wait_for_requests
expect(page).to have_content('JIRA activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
end
@@ -89,7 +89,7 @@ feature 'Setup Jira service', :feature, :js do
click_button('Save changes')
expect(page).to have_content('JIRA settings saved, but not activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index d87985f1c92..584d3ed8f42 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -9,8 +9,8 @@ feature 'Setup Mattermost slash commands', :feature, :js do
before do
stub_mattermost_setting(enabled: mattermost_enabled)
project.team << [user, :master]
- gitlab_sign_in(user)
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ sign_in(user)
+ visit edit_project_service_path(project, service)
end
describe 'user visits the mattermost slash command config page' do
@@ -30,7 +30,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
fill_in 'service_token', with: token
click_on 'Save changes'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
end
@@ -41,7 +41,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
check 'service_active'
click_on 'Save changes'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Mattermost slash commands activated.')
end
diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb
index 50707e6a49f..709cd1226c3 100644
--- a/spec/features/projects/services/slack_service_spec.rb
+++ b/spec/features/projects/services/slack_service_spec.rb
@@ -9,11 +9,11 @@ feature 'Projects > Slack service > Setup events', feature: true do
service.fields
service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, pipeline_channel: 6, wiki_page_channel: 7)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user can filter events by channel' do
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ visit edit_project_service_path(project, service)
expect(page.find_field("service_push_channel").value).to have_content '1'
expect(page.find_field("service_issue_channel").value).to have_content '2'
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index 3fae38c1799..4efe484262a 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -7,8 +7,8 @@ feature 'Slack slash commands', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ sign_in(user)
+ visit edit_project_service_path(project, service)
end
it 'shows a token placeholder' do
@@ -25,7 +25,7 @@ feature 'Slack slash commands', feature: true do
fill_in 'service_token', with: 'token'
click_on 'Save'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
@@ -34,7 +34,7 @@ feature 'Slack slash commands', feature: true do
check 'service_active'
click_on 'Save'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands activated.')
end
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index a59374b37ea..13313bfde24 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -4,10 +4,10 @@ feature 'Integration settings', feature: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:role) { :developer }
- let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) }
+ let(:integrations_path) { project_settings_integrations_path(project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
@@ -109,7 +109,7 @@ feature 'Integration settings', feature: true do
scenario 'show list of hook logs' do
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
expect(page).to have_content('Recent Deliveries')
expect(page).to have_content(hook_log.url)
@@ -117,7 +117,7 @@ feature 'Integration settings', feature: true do
scenario 'show hook log details' do
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
click_link 'View details'
expect(page).to have_content("POST #{hook_log.url}")
@@ -129,11 +129,11 @@ feature 'Integration settings', feature: true do
WebMock.stub_request(:post, hook.url)
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
click_link 'View details'
click_link 'Resend Request'
- expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook))
+ expect(current_path).to eq(edit_project_hook_path(project, hook))
end
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 f2af14ceab2..ecaf65c4ad9 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -1,14 +1,12 @@
require 'spec_helper'
feature 'Project settings > Merge Requests', feature: true, js: true do
- include GitlabRoutingHelper
-
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when Merge Request and Pipelines are initially enabled' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index c33fbd49d21..724cfa10e72 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,16 +1,14 @@
require 'spec_helper'
feature "Pipelines settings", feature: true do
- include GitlabRoutingHelper
-
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:role) { :developer }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
- visit namespace_project_pipelines_settings_path(project.namespace, project)
+ visit project_pipelines_settings_path(project)
end
context 'for developer' do
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 35cd0d6e832..98539518f6c 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -7,14 +7,14 @@ feature 'Repository settings', feature: true do
background do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'for developer' do
given(:role) { :developer }
scenario 'is not allowed to view' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
expect(page.status_code).to eq(404)
end
@@ -32,7 +32,7 @@ feature 'Repository settings', feature: true do
project.deploy_keys << private_deploy_key
project.deploy_keys << public_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
expect(page.status_code).to eq(200)
expect(page).to have_content('private_deploy_key')
@@ -40,7 +40,7 @@ feature 'Repository settings', feature: true do
end
scenario 'add a new deploy key' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
fill_in 'deploy_key_title', with: 'new_deploy_key'
fill_in 'deploy_key_key', with: new_ssh_key
@@ -53,7 +53,7 @@ feature 'Repository settings', feature: true do
scenario 'edit an existing deploy key' do
project.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_link('Edit')
@@ -70,7 +70,7 @@ feature 'Repository settings', feature: true do
project2.team << [user, role]
project2.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_link('Edit')
@@ -84,7 +84,7 @@ feature 'Repository settings', feature: true do
scenario 'remove an existing deploy key' do
project.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_button('Remove')
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 18c71dee41b..32d8f1fd16a 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -6,8 +6,8 @@ feature 'Visibility settings', feature: true, js: true do
context 'as owner' do
before do
- gitlab_sign_in(user)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit edit_project_path(project)
end
scenario 'project visibility select is available' do
@@ -32,8 +32,8 @@ feature 'Visibility settings', feature: true, js: true do
before do
project.team << [master_user, :master]
- gitlab_sign_in(master_user)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(master_user)
+ visit edit_project_path(project)
end
scenario 'project visibility is locked' do
diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index cec79277c33..8dd70e07b30 100644
--- a/spec/features/projects/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
@@ -7,8 +7,8 @@ feature 'Project shortcuts', feature: true do
describe 'On a project', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_path(project.namespace, project)
+ sign_in user
+ visit project_path(project)
end
describe 'pressing "i"' do
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index c75d6dbc307..06d32423a13 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -17,9 +17,9 @@ feature 'Create Snippet', :js, feature: true do
context 'when a user is authenticated' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
click_on('New snippet')
end
@@ -77,7 +77,7 @@ feature 'Create Snippet', :js, feature: true do
it 'shows a public snippet on the index page but not the New snippet button' do
snippet = create(:project_snippet, :public, project: project)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
expect(page).to have_content(snippet.title)
expect(page).not_to have_content('New snippet')
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index 9e73ba4123b..52698fe1fa3 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -7,7 +7,7 @@ feature 'Project snippet', :js, feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'Ruby file' do
@@ -15,7 +15,7 @@ feature 'Project snippet', :js, feature: true do
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
wait_for_requests
end
@@ -46,7 +46,7 @@ feature 'Project snippet', :js, feature: true do
context 'visiting directly' do
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
wait_for_requests
end
@@ -118,7 +118,7 @@ feature 'Project snippet', :js, feature: true do
context 'visiting with a line number anchor' do
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
+ visit project_snippet_path(project, snippet, anchor: 'L1')
wait_for_requests
end
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
index 80dbffaffc7..513a05151b2 100644
--- a/spec/features/projects/snippets_spec.rb
+++ b/spec/features/projects/snippets_spec.rb
@@ -10,7 +10,7 @@ describe 'Project snippets', :js, feature: true do
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
end
it_behaves_like 'paginated snippets'
@@ -18,7 +18,7 @@ describe 'Project snippets', :js, feature: true do
context 'list content' do
it 'contains all project snippets' do
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
expect(page).to have_selector('.snippet-row', count: 2)
@@ -29,8 +29,8 @@ describe 'Project snippets', :js, feature: true do
context 'when submitting a note' do
before do
- gitlab_sign_in :admin
- visit namespace_project_snippet_path(project.namespace, project, snippets[0])
+ sign_in(create(:admin))
+ visit project_snippet_path(project, snippets[0])
end
it 'should have autocomplete' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 63eb97d5a92..007910bb931 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -8,17 +8,17 @@ describe 'Subgroup Issuables', :feature, :js, :nested_groups do
before do
project.add_master(user)
- gitlab_sign_in user
+ sign_in user
end
it 'shows the full subgroup title when issues index page is empty' do
- visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+ visit project_issues_path(project)
expect_to_have_full_subgroup_title
end
it 'shows the full subgroup title when merge requests index page is empty' do
- visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+ visit project_merge_requests_path(project)
expect_to_have_full_subgroup_title
end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index ca00a51aa3c..34c5e59c3e5 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -23,20 +23,18 @@ feature 'Download buttons in tags page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking tags' do
context 'with artifacts' do
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{tag}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 135584e5bf8..4f2e0a76a65 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project Tree RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_tree_path(project.namespace, project, :master) }
+ let(:path) { project_tree_path(project, :master) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb
index f375e1215db..5e302da8a63 100644
--- a/spec/features/projects/user_create_dir_spec.rb
+++ b/spec/features/projects/user_create_dir_spec.rb
@@ -6,9 +6,9 @@ feature 'New directory creation', feature: true, js: true do
given(:project) { create(:project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
- visit namespace_project_tree_path(project.namespace, project, 'master')
+ visit project_tree_path(project, 'master')
open_new_directory_modal
fill_in 'dir_name', with: 'new_directory'
end
@@ -51,7 +51,7 @@ feature 'New directory creation', feature: true, js: true do
expect(page).to have_content 'New Merge Request'
expect(page).to have_content "From #{new_branch_name} into master"
expect(page).to have_content 'Add new directory'
- expect(current_path).to eq(new_namespace_project_merge_request_path(project.namespace, project))
+ expect(current_path).to eq(project_new_merge_request_path(project))
end
end
end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index 29f1eb8d73e..1c3791f63ac 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -18,7 +18,7 @@ feature 'User creates a project', js: true do
project = Project.last
- expect(current_path).to eq(namespace_project_path(project.namespace, project))
+ expect(current_path).to eq(project_path(project))
expect(page).to have_content('Empty')
expect(page).to have_content('git init')
expect(page).to have_content('git remote')
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index f6a640b90b4..2a316a0d0db 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -50,9 +50,9 @@ describe 'View on environment', js: true do
let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: branch_name) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
wait_for_requests
end
@@ -66,9 +66,9 @@ describe 'View on environment', js: true do
context 'when visiting a comparison for the branch' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name)
+ visit project_compare_path(project, from: 'master', to: branch_name)
wait_for_requests
end
@@ -80,9 +80,9 @@ describe 'View on environment', js: true do
context 'when visiting a comparison for the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha)
+ visit project_compare_path(project, from: 'master', to: sha)
wait_for_requests
end
@@ -94,9 +94,9 @@ describe 'View on environment', js: true do
context 'when visiting a blob on the branch' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path))
+ visit project_blob_path(project, File.join(branch_name, file_path))
wait_for_requests
end
@@ -108,9 +108,9 @@ describe 'View on environment', js: true do
context 'when visiting a blob on the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path))
+ visit project_blob_path(project, File.join(sha, file_path))
wait_for_requests
end
@@ -122,9 +122,9 @@ describe 'View on environment', js: true do
context 'when visiting the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_commit_path(project.namespace, project, sha)
+ visit project_commit_path(project, sha)
wait_for_requests
end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index fd6c09943e3..231e8eed4fb 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -16,9 +16,9 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
project.team << [user, :master]
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
find('.shortcuts-wiki').trigger('click')
end
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index ab0ed9b8204..ea816082479 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -8,8 +8,8 @@ feature 'Wiki shortcuts', :feature, :js do
end
before do
- gitlab_sign_in(user)
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ sign_in(user)
+ visit project_wiki_path(project, wiki_page)
end
scenario 'Visit edit wiki page using "e" keyboard shortcut' do
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index a477dcf7ee9..9d66f482c8d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -1,20 +1,23 @@
require 'spec_helper'
-feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
+feature 'Projects > Wiki > User creates wiki page', :js do
let(:user) { create(:user) }
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_path(project.namespace, project)
- find('.shortcuts-wiki').trigger('click')
+ visit project_path(project)
end
context 'in the user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
context 'when wiki is empty' do
+ before do
+ find('.shortcuts-wiki').trigger('click')
+ end
+
scenario 'commit message field has value "Create home"' do
expect(page).to have_field('wiki[message]', with: 'Create home')
end
@@ -67,10 +70,11 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ find('.shortcuts-wiki').trigger('click')
end
context 'via the "new wiki page" page' do
- scenario 'when the wiki page has a single word name', js: true do
+ scenario 'when the wiki page has a single word name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -91,7 +95,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
expect(page).to have_content('My awesome wiki!')
end
- scenario 'when the wiki page has spaces in the name', js: true do
+ scenario 'when the wiki page has spaces in the name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -112,7 +116,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
expect(page).to have_content('My awesome wiki!')
end
- scenario 'when the wiki page has hyphens in the name', js: true do
+ scenario 'when the wiki page has hyphens in the name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -134,7 +138,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
end
end
- scenario 'content has autocomplete', :js do
+ scenario 'content has autocomplete' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -156,6 +160,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
let(:project) { create(:project, namespace: create(:group, :public)) }
context 'when wiki is empty' do
+ before do
+ find('.shortcuts-wiki').trigger('click')
+ end
+
scenario 'commit message field has value "Create home"' do
expect(page).to have_field('wiki[message]', with: 'Create home')
end
@@ -175,9 +183,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ find('.shortcuts-wiki').trigger('click')
end
- scenario 'via the "new wiki page" page', js: true do
+ scenario 'via the "new wiki page" page' do
click_link 'New page'
page.within '#modal-new-wiki' do
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index 7d31122af35..9445b88af8d 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -13,11 +13,11 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'Visit Wiki Page Current Commit' do
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ visit project_wiki_path(project, wiki_page)
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}")
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 64a30438681..425195840d8 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -6,9 +6,9 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
background do
project.team << [user, :master]
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_wikis_path(project.namespace, project)
+ visit project_wikis_path(project)
end
context 'in the user namespace' do
diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
index 8a88ab247f3..13e882ad665 100644
--- a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
@@ -15,7 +15,7 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
WikiPages::UpdateService.new(
project,
user,
@@ -26,18 +26,13 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
end
scenario 'Visit Wiki Page Current Commit' do
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ visit project_wiki_path(project, wiki_page)
expect(page).to have_selector('a.btn', text: 'Edit')
end
scenario 'Visit Wiki Page Historical Commit' do
- visit namespace_project_wiki_path(
- project.namespace,
- project,
- wiki_page,
- version_id: old_page_version_id
- )
+ visit project_wiki_path(project, wiki_page, version_id: old_page_version_id)
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index 36799925167..2234af1d795 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -5,7 +5,7 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when repository is disabled for project' do
@@ -27,14 +27,10 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do
end
it 'displays the correct URL for the link' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_link(
'some link',
- href: namespace_project_wiki_path(
- project.namespace,
- project,
- 'other-page'
- )
+ href: project_wiki_path(project, 'other-page')
)
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 7e8a703db93..10c7e5934e4 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -3,10 +3,10 @@ require 'spec_helper'
feature 'Project', feature: true do
describe 'description' do
let(:project) { create(:project, :repository) }
- let(:path) { namespace_project_path(project.namespace, project) }
+ let(:path) { project_path(project) }
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
it 'parses Markdown' do
@@ -39,9 +39,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace) }
before do
- gitlab_sign_in user
+ sign_in user
create(:forked_project_link, forked_to_project: project)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
it 'removes fork' do
@@ -60,9 +60,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace, name: 'project1') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
it 'removes a project' do
@@ -79,9 +79,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'clicks toggle and shows dropdown', js: true do
@@ -98,10 +98,10 @@ feature 'Project', feature: true do
context 'on issues page', js: true do
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_user(user, Gitlab::Access::MASTER)
project2.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'clicks toggle and shows dropdown' do
@@ -123,8 +123,8 @@ feature 'Project', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_path(project.namespace, project)
+ sign_in user
+ visit project_path(project)
end
it 'has working links to files' do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 20b8e10f0f7..8a3574546c2 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -5,7 +5,7 @@ feature 'Protected Branches', feature: true, js: true do
let(:project) { create(:project, :repository) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
def set_protected_branch_name(branch_name)
@@ -16,7 +16,7 @@ feature 'Protected Branches', feature: true, js: true do
describe "explicit protected branches" do
it "allows creating explicit protected branches" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -29,7 +29,7 @@ feature 'Protected Branches', feature: true, js: true do
commit = create(:commit, project: project)
project.repository.add_branch(user, 'some-branch', commit.id)
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -37,7 +37,7 @@ feature 'Protected Branches', feature: true, js: true do
end
it "displays an error message if the named branch does not exist" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -47,7 +47,7 @@ feature 'Protected Branches', feature: true, js: true do
describe "wildcard protected branches" do
it "allows creating protected branches with a wildcard" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -60,7 +60,7 @@ feature 'Protected Branches', feature: true, js: true do
project.repository.add_branch(user, 'production-stable', 'master')
project.repository.add_branch(user, 'staging-stable', 'master')
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -72,11 +72,11 @@ feature 'Protected Branches', feature: true, js: true do
project.repository.add_branch(user, 'staging-stable', 'master')
project.repository.add_branch(user, 'development', 'master')
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
click_on "2 matching branches"
within(".protected-branches-list") do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 73a80692154..7a22cf60996 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -5,7 +5,7 @@ feature 'Projected Tags', feature: true, js: true do
let(:project) { create(:project, :repository) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
def set_protected_tag_name(tag_name)
@@ -17,7 +17,7 @@ feature 'Projected Tags', feature: true, js: true do
describe "explicit protected tags" do
it "allows creating explicit protected tags" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -30,7 +30,7 @@ feature 'Projected Tags', feature: true, js: true do
commit = create(:commit, project: project)
project.repository.add_tag(user, 'some-tag', commit.id)
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -38,7 +38,7 @@ feature 'Projected Tags', feature: true, js: true do
end
it "displays an error message if the named tag does not exist" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -48,7 +48,7 @@ feature 'Projected Tags', feature: true, js: true do
describe "wildcard protected tags" do
it "allows creating protected tags with a wildcard" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
@@ -61,7 +61,7 @@ feature 'Projected Tags', feature: true, js: true do
project.repository.add_tag(user, 'production-stable', 'master')
project.repository.add_tag(user, 'staging-stable', 'master')
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
@@ -73,11 +73,11 @@ feature 'Projected Tags', feature: true, js: true do
project.repository.add_tag(user, 'staging-stable', 'master')
project.repository.add_tag(user, 'development', 'master')
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
click_on "2 matching tags"
within(".protected-tags-list") do
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 12049822753..d82ebe02f77 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -8,14 +8,14 @@ describe 'Reportable note on commit', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'a normal note' do
let!(:note) { create(:note_on_commit, commit_id: sample_commit.id, project: project) }
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'reportable note'
@@ -25,7 +25,7 @@ describe 'Reportable note on commit', :feature, :js do
let!(:note) { create(:diff_note_on_commit, commit_id: sample_commit.id, project: project) }
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index ca2a7f41496..cb1cb1a1417 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -8,9 +8,9 @@ describe 'Reportable note on issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index 8e75b4af3eb..8a531b9a9e9 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -7,9 +7,9 @@ describe 'Reportable note on merge request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'a normal note' do
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index 5bee4a31379..f560a0ebfd9 100644
--- a/spec/features/reportable_note/snippets_spec.rb
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -6,7 +6,7 @@ describe 'Reportable note on snippets', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'on project snippet' do
@@ -14,7 +14,7 @@ describe 'Reportable note on snippets', :feature, :js do
let!(:note) { create(:note_on_project_snippet, noteable: snippet, project: project) }
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index ea18879b4bf..1725b70acf3 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,12 +1,10 @@
require 'spec_helper'
describe "Runners" do
- include GitlabRoutingHelper
-
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "specific runners" do
@@ -124,7 +122,7 @@ describe "Runners" do
end
scenario 'user checks default configuration' do
- visit namespace_project_runner_path(project.namespace, project, runner)
+ visit project_runner_path(project, runner)
expect(page).to have_content 'Can run untagged jobs Yes'
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 64469f999af..12ef23440b7 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -9,7 +9,7 @@ describe "Search", feature: true do
let!(:issue2) { create(:issue, project: project, author: user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :reporter]
visit search_path
end
@@ -88,7 +88,7 @@ describe "Search", feature: true do
end
it 'finds comment' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: note.note
@@ -111,7 +111,7 @@ describe "Search", feature: true do
project: project)
# Must visit project dashboard since global search won't search
# everything (e.g. comments, snippets, etc.)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: note.note
@@ -125,7 +125,7 @@ describe "Search", feature: true do
it 'finds a commit' do
project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: 'add'
@@ -139,7 +139,7 @@ describe "Search", feature: true do
it 'finds a code' do
project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: 'application.js'
@@ -156,7 +156,7 @@ describe "Search", feature: true do
describe 'Right header search field', feature: true do
it 'allows enter key to search', js: true do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
fill_in 'search', with: 'gitlab'
find('#search').native.send_keys(:enter)
@@ -167,7 +167,7 @@ describe "Search", feature: true do
describe 'Search in project page' do
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows top right search form' do
@@ -256,7 +256,7 @@ describe "Search", feature: true do
click_button 'Search'
- expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+ expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
end
it 'redirects to single commit regardless of query case' do
@@ -264,7 +264,7 @@ describe "Search", feature: true do
click_button 'Search'
- expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+ expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
end
it 'holds on /search page when the only commit is found by message' do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index f33406a40a7..1000a0bdd89 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -13,7 +13,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -239,7 +239,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/merge_requests/new" do
- subject { new_namespace_project_merge_request_path(project.namespace, project) }
+ subject { project_new_merge_request_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -253,7 +253,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -272,7 +272,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -291,7 +291,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/integrations" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -305,7 +305,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -320,7 +320,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -334,7 +334,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
context "when allowed for public and internal" do
before do
@@ -372,7 +372,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
context "when allowed for public and internal" do
before do
@@ -410,7 +410,7 @@ describe "Internal Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
context 'when allowed for public and internal' do
before do
@@ -446,7 +446,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -460,7 +460,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -475,7 +475,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -490,7 +490,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -504,7 +504,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Internal Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index b676c236758..94d759393ca 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -13,7 +13,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -244,7 +244,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -263,7 +263,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/namespace/hooks" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -277,7 +277,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -304,7 +304,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -330,7 +330,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -358,7 +358,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -391,7 +391,7 @@ describe "Private Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -421,7 +421,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -436,7 +436,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -451,7 +451,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -465,7 +465,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -479,7 +479,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -493,7 +493,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules/new" do
- subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+ subject { new_project_pipeline_schedule_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -507,7 +507,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+ subject { new_project_pipeline_schedule_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -529,7 +529,7 @@ describe "Private Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 16a1331b2f3..d45e1dbc09b 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -13,7 +13,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -125,7 +125,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
context "when allowed for public" do
before do
@@ -192,7 +192,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
context "when allowed for public" do
before do
@@ -230,7 +230,7 @@ describe "Public Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
context 'when allowed for public' do
before do
@@ -266,7 +266,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -280,7 +280,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -295,7 +295,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -310,7 +310,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -324,7 +324,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -340,7 +340,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -353,7 +353,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -367,7 +367,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -381,7 +381,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -396,7 +396,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -410,7 +410,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -424,7 +424,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -438,7 +438,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -452,7 +452,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/merge_requests/new" do
- subject { new_namespace_project_merge_request_path(project.namespace, project) }
+ subject { project_new_merge_request_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -466,7 +466,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -485,7 +485,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -504,7 +504,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/integrations" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Public Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 2659b3ee3ec..2420caa88c4 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -9,7 +9,7 @@ describe "Internal Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -23,7 +23,7 @@ describe "Internal Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -38,7 +38,7 @@ describe "Internal Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id" do
context "for an internal snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -52,7 +52,7 @@ describe "Internal Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -68,7 +68,7 @@ describe "Internal Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id/raw" do
context "for an internal snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { raw_project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -82,7 +82,7 @@ describe "Internal Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 6eb9f163bd5..0b8548a675b 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -8,7 +8,7 @@ describe "Private Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -22,7 +22,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -36,7 +36,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/:id for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -50,7 +50,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/:id/raw for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index f3329d0bc96..153f8f964a6 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -10,7 +10,7 @@ describe "Public Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -24,7 +24,7 @@ describe "Public Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -39,7 +39,7 @@ describe "Public Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id" do
context "for a public snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+ subject { project_snippet_path(project, public_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -53,7 +53,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for an internal snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -67,7 +67,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id/raw" do
context "for a public snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) }
+ subject { raw_project_snippet_path(project, public_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for an internal snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { raw_project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index ec75817b942..97d1c2d65e6 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -6,7 +6,7 @@ feature 'Explore Snippets', feature: true do
let!(:private_snippet) { create(:personal_snippet, :private) }
scenario 'User should see snippets that are not private' do
- gitlab_sign_in create(:user)
+ sign_in create(:user)
visit explore_snippets_path
expect(page).to have_content(public_snippet.title)
@@ -15,7 +15,7 @@ feature 'Explore Snippets', feature: true do
end
scenario 'External user should see only public snippets' do
- gitlab_sign_in create(:user, :external)
+ sign_in create(:user, :external)
visit explore_snippets_path
expect(page).to have_content(public_snippet.title)
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index 3babb1c02cc..fb3e75f2102 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -5,7 +5,7 @@ feature 'Internal Snippets', feature: true, js: true do
describe 'normal user' do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
scenario 'sees internal snippets' do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index c7e2e3d8a34..17e93209f0c 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -14,7 +14,7 @@ describe 'Comments on personal snippets', :js, feature: true do
let!(:other_note) { create(:note_on_personal_snippet) }
before do
- gitlab_sign_in user
+ sign_in user
visit snippet_path(snippet)
end
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index 4c21e7321f4..5483df39a8b 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -5,7 +5,7 @@ feature 'Search Snippets', feature: true do
public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
private_snippet = create(:personal_snippet, :private, title: 'Middle and End')
- gitlab_sign_in private_snippet.author
+ sign_in private_snippet.author
visit dashboard_snippets_path
page.within '.search' do
@@ -41,7 +41,7 @@ feature 'Search Snippets', feature: true do
CONTENT
)
- gitlab_sign_in create(:user)
+ sign_in create(:user)
visit dashboard_snippets_path
page.within '.search' do
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index ac5c14ed427..57dec14b480 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,10 +1,12 @@
require 'rails_helper'
-feature 'Create Snippet', :js, feature: true do
+feature 'User creates snippet', :js, feature: true do
include DropzoneHelper
+ let(:user) { create(:user) }
+
before do
- gitlab_sign_in :user
+ sign_in(user)
visit new_snippet_path
end
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
new file mode 100644
index 00000000000..162c2c9e730
--- /dev/null
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+feature 'User deletes snippet', feature: true do
+ let(:user) { create(:user) }
+ let(:content) { 'puts "test"' }
+ let(:snippet) { create(:personal_snippet, :public, content: content, author: user) }
+
+ before do
+ sign_in(user)
+
+ visit snippet_path(snippet)
+ end
+
+ it 'deletes the snippet' do
+ first(:link, 'Delete').click
+
+ expect(page).not_to have_content(snippet.title)
+ end
+end
diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 860e1b156d6..cff64423873 100644
--- a/spec/features/snippets/edit_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Edit Snippet', :js, feature: true do
+feature 'User edits snippet', :js, feature: true do
include DropzoneHelper
let(:file_name) { 'test.rb' }
@@ -10,7 +10,7 @@ feature 'Edit Snippet', :js, feature: true do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_snippet_path(snippet)
wait_for_requests
@@ -27,7 +27,7 @@ feature 'Edit Snippet', :js, feature: true do
it 'updates the snippet with files attached' do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
- expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
+ expect(page.find_field('personal_snippet_description').value).to have_content('banana_sample')
click_button('Save changes')
wait_for_requests
@@ -35,4 +35,24 @@ feature 'Edit Snippet', :js, feature: true do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
end
+
+ it 'updates the snippet to make it internal' do
+ choose 'Internal'
+
+ click_button 'Save changes'
+ wait_for_requests
+
+ expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+ expect(page).to have_xpath("//i[@class='fa fa-shield']")
+ end
+
+ it 'updates the snippet to make it public' do
+ choose 'Public'
+
+ click_button 'Save changes'
+ wait_for_requests
+
+ expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+ expect(page).to have_xpath("//i[@class='fa fa-globe']")
+ end
end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index b971c6aab53..019310f2326 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -7,7 +7,7 @@ feature 'User Snippets', feature: true do
let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") }
background do
- gitlab_sign_in author
+ sign_in author
visit dashboard_snippets_path
end
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
index 52db3583dac..1cef3d5c6f4 100644
--- a/spec/features/tags/master_creates_tag_spec.rb
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -6,12 +6,12 @@ feature 'Master creates tag', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'from tag list' do
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'with an invalid name displays an error' do
@@ -36,7 +36,7 @@ feature 'Master creates tag', feature: true do
create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v3.0'))
+ project_tag_path(project, 'v3.0'))
expect(page).to have_content 'v3.0'
page.within 'pre.wrap' do
expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
@@ -47,7 +47,7 @@ feature 'Master creates tag', feature: true do
create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v4.0'))
+ project_tag_path(project, 'v4.0'))
expect(page).to have_content 'v4.0'
page.within '.description' do
expect(page).to have_content 'Awesome release notes'
@@ -72,7 +72,7 @@ feature 'Master creates tag', feature: true do
context 'from new tag page' do
before do
- visit new_namespace_project_tag_path(project.namespace, project)
+ visit new_project_tag_path(project)
end
it 'description has autocomplete', :js do
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 58f33e954f9..98af1d6b4f7 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -6,8 +6,8 @@ feature 'Master deletes tag', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tags_path(project.namespace, project)
+ sign_in(user)
+ visit project_tags_path(project)
end
context 'from the tags list page', js: true do
@@ -24,12 +24,12 @@ feature 'Master deletes tag', feature: true do
scenario 'deletes the tag' do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Delete tag'
expect(current_path).to eq(
- namespace_project_tags_path(project.namespace, project))
+ project_tags_path(project))
expect(page).not_to have_content 'v1.0.0'
end
end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
index 18c8c4c511c..1b61fde7227 100644
--- a/spec/features/tags/master_updates_tag_spec.rb
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -6,8 +6,8 @@ feature 'Master updates tag', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tags_path(project.namespace, project)
+ sign_in(user)
+ visit project_tags_path(project)
end
context 'from the tags list page' do
@@ -20,7 +20,7 @@ feature 'Master updates tag', feature: true do
click_button 'Save changes'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ project_tag_path(project, 'v1.1.0'))
expect(page).to have_content 'v1.1.0'
expect(page).to have_content 'Awesome release notes'
end
@@ -45,7 +45,7 @@ feature 'Master updates tag', feature: true do
click_button 'Save changes'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ project_tag_path(project, 'v1.1.0'))
expect(page).to have_content 'v1.1.0'
expect(page).to have_content 'Awesome release notes'
end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
index 3c21fa06694..fb910feae34 100644
--- a/spec/features/tags/master_views_tags_spec.rb
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -5,19 +5,19 @@ feature 'Master views tags', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when project has no tags' do
let(:project) { create(:project_empty_repo) }
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_on 'README'
fill_in :commit_message, with: 'Add a README file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes'
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'displays a specific message' do
@@ -30,15 +30,15 @@ feature 'Master views tags', feature: true do
let(:repository) { project.repository }
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'avoids a N+1 query in branches index' do
- control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_tags_path(project.namespace, project) }.count
+ control_count = ActiveRecord::QueryRecorder.new { visit project_tags_path(project) }.count
%w(one two three four five).each { |tag| repository.add_tag(user, tag, 'master', 'foo') }
- expect { visit namespace_project_tags_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+ expect { visit project_tags_path(project) }.not_to exceed_query_limit(control_count)
end
scenario 'views the tags list page' do
@@ -49,7 +49,7 @@ feature 'Master views tags', feature: true do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
expect(page).to have_content 'v1.0.0'
expect(page).to have_content 'This tag has no release notes.'
end
@@ -59,24 +59,24 @@ feature 'Master views tags', feature: true do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Browse files'
expect(current_path).to eq(
- namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+ project_tree_path(project, 'v1.0.0'))
end
scenario 'has a button to browse commits' do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Browse commits'
expect(current_path).to eq(
- namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+ project_commits_path(project, 'v1.0.0'))
end
end
end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 51b1b8e2328..dfc362321aa 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -59,7 +59,7 @@ feature 'Task Lists', feature: true do
end
def visit_issue(project, issue)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
describe 'for Issues', feature: true do
@@ -98,7 +98,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
@@ -116,7 +116,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("0 of 1 task completed")
end
@@ -135,7 +135,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("1 of 1 task completed")
end
@@ -242,7 +242,7 @@ feature 'Task Lists', feature: true do
describe 'for Merge Requests' do
def visit_merge_request(project, merge)
- visit namespace_project_merge_request_path(project.namespace, project, merge)
+ visit project_merge_request_path(project, merge)
end
describe 'multiple tasks' do
@@ -281,7 +281,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
@@ -298,7 +298,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("0 of 1 task completed")
end
end
@@ -315,7 +315,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("1 of 1 task completed")
end
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 5af2c0e9035..47d5f94f54e 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -14,7 +14,7 @@ feature 'Triggers', feature: true, js: true do
@project.team << [user2, :master]
@project.team << [guest_user, :guest]
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
describe 'create trigger workflow' do
@@ -42,7 +42,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'click on edit trigger opens edit trigger page' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if edit page has correct descrption
find('a[title="Edit"]').click
@@ -51,7 +51,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'edit trigger and save' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if edit page opens, then fill in new description and save
find('a[title="Edit"]').click
@@ -67,7 +67,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
create(:ci_trigger, owner: nil, project: @project)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
find('a[title="Edit"]').click
@@ -84,7 +84,7 @@ feature 'Triggers', feature: true, js: true do
describe 'trigger "Take ownership" workflow' do
before(:each) do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
scenario 'button "Take ownership" has correct alert' do
@@ -106,7 +106,7 @@ feature 'Triggers', feature: true, js: true do
describe 'trigger "Revoke" workflow' do
before(:each) do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
scenario 'button "Revoke" has correct alert' do
@@ -131,7 +131,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "legacy" badge for legacy trigger' do
create(:ci_trigger, owner: nil, project: @project)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
expect(page.find('.triggers-list')).to have_content 'legacy'
@@ -140,7 +140,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
expect(page.find('.triggers-list')).to have_content 'invalid'
@@ -150,7 +150,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
@@ -163,7 +163,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger shows full token and has copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 797b7b3d50d..32784de1613 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -5,7 +5,7 @@ feature 'User uploads avatar to group', feature: true do
user = create(:user)
group = create(:group)
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_group_path(group)
attach_file(
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index a3f8027f4da..82c356735b9 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
feature 'User uploads avatar to profile', feature: true do
scenario 'they see their new avatar' do
user = create(:user)
- gitlab_sign_in(user)
+ sign_in(user)
visit profile_path
attach_file(
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 77a1012762d..01f10ca0933 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -8,8 +8,8 @@ feature 'User uploads file to note', feature: true do
let(:issue) { create(:issue, project: project, author: user) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
context 'before uploading' do
diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb
index 7538a6e4a04..93768aa46df 100644
--- a/spec/features/user_callout_spec.rb
+++ b/spec/features/user_callout_spec.rb
@@ -6,7 +6,7 @@ describe 'User Callouts', js: true do
let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 1bd7e038939..670e8dda916 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'User can display performacne bar', :js do
+describe 'User can display performance bar', :js do
shared_examples 'performance bar is disabled' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
@@ -27,28 +27,30 @@ describe 'User can display performacne bar', :js do
find('body').native.send_keys('pb')
end
- it 'does not show the performance bar by default' do
- expect(page).not_to have_css('#peek')
+ it 'shows the performance bar' do
+ expect(page).to have_css('#peek')
end
end
end
+ let(:group) { create(:group) }
+
context 'when user is logged-out' do
before do
visit root_path
end
- context 'when the gitlab_performance_bar feature is disabled' do
+ context 'when the performance_bar feature is disabled' do
before do
- Feature.disable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: nil)
end
it_behaves_like 'performance bar is disabled'
end
- context 'when the gitlab_performance_bar feature is enabled' do
+ context 'when the performance_bar feature is enabled' do
before do
- Feature.enable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: group.id)
end
it_behaves_like 'performance bar is disabled'
@@ -57,22 +59,25 @@ describe 'User can display performacne bar', :js do
context 'when user is logged-in' do
before do
- gitlab_sign_in(create(:user))
+ user = create(:user)
+
+ sign_in(user)
+ group.add_guest(user)
visit root_path
end
- context 'when the gitlab_performance_bar feature is disabled' do
+ context 'when the performance_bar feature is disabled' do
before do
- Feature.disable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: nil)
end
it_behaves_like 'performance bar is disabled'
end
- context 'when the gitlab_performance_bar feature is enabled' do
+ context 'when the performance_bar feature is enabled' do
before do
- Feature.enable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: group.id)
end
it_behaves_like 'performance bar is enabled'
diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb
index 377b1a0148f..797ed0e6437 100644
--- a/spec/features/users/projects_spec.rb
+++ b/spec/features/users/projects_spec.rb
@@ -8,7 +8,7 @@ describe 'Projects tab on a user profile', :feature, :js do
before do
allow(Project).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(user)
+ sign_in(user)
visit user_path(user)
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 797b317a9bb..7c5abe54d56 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
feature 'User RSS' do
+ let(:user) { create(:user) }
let(:path) { user_path(create(:user)) }
context 'when signed in' do
before do
- gitlab_sign_in(create(:user))
+ sign_in(user)
visit path
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 74c5cbd7887..42738b137af 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -24,7 +24,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
let!(:other_snippet) { create(:snippet, :public) }
it 'contains only internal and public snippets of a user when a user is logged in' do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
wait_for_requests
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index 85085bf305a..dd770fe5043 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -6,11 +6,11 @@ describe 'Project variables', js: true do
let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
project.variables << variable
- visit namespace_project_settings_ci_cd_path(project.namespace, project)
+ visit project_settings_ci_cd_path(project)
end
it 'shows list of variables' do
@@ -24,7 +24,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('key')
expect(page).to have_content('No')
@@ -36,7 +36,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: '')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('new_key')
end
@@ -48,7 +48,7 @@ describe 'Project variables', js: true do
check('Protected')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('key')
expect(page).to have_content('Yes')
@@ -82,7 +82,7 @@ describe 'Project variables', js: true do
it 'deletes variable' do
page.within('.variables-table') do
- find('.btn-variable-delete').click
+ click_on 'Remove'
end
expect(page).not_to have_selector('variables-table')
@@ -90,7 +90,7 @@ describe 'Project variables', js: true do
it 'edits variable' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -104,7 +104,7 @@ describe 'Project variables', js: true do
it 'edits variable with empty value' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -117,7 +117,7 @@ describe 'Project variables', js: true do
it 'edits variable to be protected' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -132,7 +132,7 @@ describe 'Project variables', js: true do
project.variables.first.update(protected: true)
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 8ace1fb5751..bef4fd44331 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -59,6 +59,23 @@ describe IssuesFinder do
end
end
+ context 'filtering by group milestone' do
+ let!(:group) { create(:group, :public) }
+ let(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let(:params) { { milestone_title: group_milestone.title } }
+
+ before do
+ project2.update(namespace: group)
+ issue2.update(milestone: group_milestone)
+ issue3.update(milestone: group_milestone)
+ end
+
+ it 'returns issues assigned to that group milestone' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+
context 'filtering by no milestone' do
let(:params) { { milestone_title: Milestone::None.title } }
@@ -295,22 +312,121 @@ describe IssuesFinder do
end
end
- describe '.not_restricted_by_confidentiality' do
- let(:authorized_user) { create(:user) }
- let(:project) { create(:empty_project, namespace: authorized_user.namespace) }
- let!(:public_issue) { create(:issue, project: project) }
- let!(:confidential_issue) { create(:issue, project: project, confidential: true) }
+ describe '#with_confidentiality_access_check' do
+ let(:guest) { create(:user) }
+ set(:authorized_user) { create(:user) }
+ set(:project) { create(:empty_project, namespace: authorized_user.namespace) }
+ set(:public_issue) { create(:issue, project: project) }
+ set(:confidential_issue) { create(:issue, project: project, confidential: true) }
- it 'returns non confidential issues for nil user' do
- expect(described_class.send(:not_restricted_by_confidentiality, nil)).to include(public_issue)
- end
+ context 'when no project filter is given' do
+ let(:params) { {} }
+
+ context 'for an anonymous user' do
+ subject { described_class.new(nil, params).with_confidentiality_access_check }
- it 'returns non confidential issues for user not authorized for the issues projects' do
- expect(described_class.send(:not_restricted_by_confidentiality, user)).to include(public_issue)
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+ end
+
+ context 'for a user without project membership' do
+ subject { described_class.new(user, params).with_confidentiality_access_check }
+
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+ end
+
+ context 'for a guest user' do
+ subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+ before do
+ project.add_guest(guest)
+ end
+
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+ end
+
+ context 'for a project member with access to view confidential issues' do
+ subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
+
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
+ end
end
- it 'returns all issues for user authorized for the issues projects' do
- expect(described_class.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue)
+ context 'when searching within a specific project' do
+ let(:params) { { project_id: project.id } }
+
+ context 'for an anonymous user' do
+ subject { described_class.new(nil, params).with_confidentiality_access_check }
+
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+
+ it 'does not filter by confidentiality' do
+ expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+ subject
+ end
+ end
+
+ context 'for a user without project membership' do
+ subject { described_class.new(user, params).with_confidentiality_access_check }
+
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+
+ it 'filters by confidentiality' do
+ expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+ subject
+ end
+ end
+
+ context 'for a guest user' do
+ subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+ before do
+ project.add_guest(guest)
+ end
+
+ it 'returns only public issues' do
+ expect(subject).to include(public_issue)
+ expect(subject).not_to include(confidential_issue)
+ end
+
+ it 'filters by confidentiality' do
+ expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+ subject
+ end
+ end
+
+ context 'for a project member with access to view confidential issues' do
+ subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
+
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
+
+ it 'does not filter by confidentiality' do
+ expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+ subject
+ end
+ end
end
end
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 1724cdba830..95d96354b77 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -49,12 +49,12 @@ describe LabelsFinder do
end
context 'filtering by group_id' do
- it 'returns labels available for any project within the group' do
+ it 'returns labels available for any non-archived project within the group' do
group_1.add_developer(user)
-
+ project_1.archive!
finder = described_class.new(user, group_id: group_1.id)
- expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5]
+ expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5]
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 5eb26de6c92..b46218bf72e 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -47,6 +47,25 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1)
end
+ context 'filtering by group milestone' do
+ let!(:group) { create(:group, :public) }
+ let(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let(:params) { { milestone_title: group_milestone.title } }
+
+ before do
+ project2.update(namespace: group)
+ merge_request2.update(milestone: group_milestone)
+ merge_request3.update(milestone: group_milestone)
+ end
+
+ it 'returns issues assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
+ end
+ end
+
context 'with created_after and created_before params' do
let(:project4) { create(:empty_project, forked_from_project: project1) }
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
new file mode 100644
index 00000000000..32ec983c5b8
--- /dev/null
+++ b/spec/finders/milestones_finder_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe MilestonesFinder do
+ let(:group) { create(:group) }
+ let(:project_1) { create(:empty_project, namespace: group) }
+ let(:project_2) { create(:empty_project, namespace: group) }
+ let!(:milestone_1) { create(:milestone, group: group, title: 'one test', due_date: Date.today) }
+ let!(:milestone_2) { create(:milestone, group: group) }
+ let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) }
+ let!(:milestone_4) { create(:milestone, project: project_2, state: 'active') }
+
+ it 'it returns milestones for projects' do
+ result = described_class.new(project_ids: [project_1.id, project_2.id], state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_3, milestone_4)
+ end
+
+ it 'returns milestones for groups' do
+ result = described_class.new(group_ids: group.id, state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_2)
+ end
+
+ it 'returns milestones for groups and projects' do
+ result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ end
+
+ context 'with filters' do
+ let(:params) do
+ {
+ project_ids: [project_1.id, project_2.id],
+ group_ids: group.id,
+ state: 'all'
+ }
+ end
+
+ before do
+ milestone_1.close
+ milestone_3.close
+ end
+
+ it 'filters by active state' do
+ params[:state] = 'active'
+ result = described_class.new(params).execute
+
+ expect(result).to contain_exactly(milestone_2, milestone_4)
+ end
+
+ it 'filters by closed state' do
+ params[:state] = 'closed'
+ result = described_class.new(params).execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_3)
+ end
+
+ it 'filters by title' do
+ result = described_class.new(params.merge(title: 'one test')).execute
+
+ expect(result.to_a).to contain_exactly(milestone_1)
+ end
+ end
+
+ context 'with order' do
+ let(:params) do
+ {
+ project_ids: [project_1.id, project_2.id],
+ group_ids: group.id,
+ state: 'all'
+ }
+ end
+
+ it "default orders by due date" do
+ result = described_class.new(params).execute
+
+ expect(result.first).to eq(milestone_1)
+ expect(result.second).to eq(milestone_3)
+ end
+
+ it "orders by parameter" do
+ result = described_class.new(params.merge(order: 'id DESC')).execute
+
+ expect(result.first).to eq(milestone_4)
+ expect(result.second).to eq(milestone_3)
+ expect(result.third).to eq(milestone_2)
+ expect(result.fourth).to eq(milestone_1)
+ end
+ end
+end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index 780b309b45e..1bab6d64388 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -45,6 +45,17 @@ describe UsersFinder do
expect(users).to contain_exactly(user, user1, user2, omniauth_user)
end
+
+ it 'filters by created_at' do
+ filtered_user_before = create(:user, created_at: 3.days.ago)
+ filtered_user_after = create(:user, created_at: Time.now + 3.days)
+
+ users = described_class.new(user,
+ created_after: 2.days.ago,
+ created_before: Time.now + 2.days).execute
+
+ expect(users.map(&:username)).not_to include([filtered_user_before.username, filtered_user_after.username])
+ end
end
context 'with an admin user' do
diff --git a/spec/fixtures/api/schemas/public_api/v3/issues.json b/spec/fixtures/api/schemas/public_api/v3/issues.json
index f2ee9c925ae..51b0822bc66 100644
--- a/spec/fixtures/api/schemas/public_api/v3/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v3/issues.json
@@ -22,7 +22,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
index 01f9fbb2c89..b5c74bcc26e 100644
--- a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
@@ -53,7 +53,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json
index 2d1c84ee93d..bd6bfc03199 100644
--- a/spec/fixtures/api/schemas/public_api/v4/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v4/issues.json
@@ -22,7 +22,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index 51642e8cbb8..60aa47c1259 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -53,7 +53,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/config/kubeconfig-without-ca.yml b/spec/fixtures/config/kubeconfig-without-ca.yml
new file mode 100644
index 00000000000..b2cb989d548
--- /dev/null
+++ b/spec/fixtures/config/kubeconfig-without-ca.yml
@@ -0,0 +1,18 @@
+---
+apiVersion: v1
+clusters:
+- name: gitlab-deploy
+ cluster:
+ server: https://kube.domain.com
+contexts:
+- name: gitlab-deploy
+ context:
+ cluster: gitlab-deploy
+ namespace: NAMESPACE
+ user: gitlab-deploy
+current-context: gitlab-deploy
+kind: Config
+users:
+- name: gitlab-deploy
+ user:
+ token: TOKEN
diff --git a/spec/fixtures/config/kubeconfig.yml b/spec/fixtures/config/kubeconfig.yml
new file mode 100644
index 00000000000..c4e8e573c32
--- /dev/null
+++ b/spec/fixtures/config/kubeconfig.yml
@@ -0,0 +1,19 @@
+---
+apiVersion: v1
+clusters:
+- name: gitlab-deploy
+ cluster:
+ server: https://kube.domain.com
+ certificate-authority-data: "UEVN\n"
+contexts:
+- name: gitlab-deploy
+ context:
+ cluster: gitlab-deploy
+ namespace: NAMESPACE
+ user: gitlab-deploy
+current-context: gitlab-deploy
+kind: Config
+users:
+- name: gitlab-deploy
+ user:
+ token: TOKEN
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 51a3e91d201..58b43805705 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -166,9 +166,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
-- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Issue by URL: <%= urls.project_issue_url(issue.project, issue) %>
- Link to issue by reference: [Issue](<%= issue.to_reference %>)
-- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
+- Link to issue by URL: [Issue](<%= urls.project_issue_url(issue.project, issue) %>)
#### MergeRequestReferenceFilter
@@ -176,9 +176,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
-- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Merge request by URL: <%= urls.project_merge_request_url(merge_request.project, merge_request) %>
- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
-- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
+- Link to merge request by URL: [Merge request](<%= urls.project_merge_request_url(merge_request.project, merge_request) %>)
#### SnippetReferenceFilter
@@ -186,9 +186,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
-- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Snippet by URL: <%= urls.project_snippet_url(snippet.project, snippet) %>
- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
-- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
+- Link to snippet by URL: [Snippet](<%= urls.project_snippet_url(snippet.project, snippet) %>)
#### CommitRangeReferenceFilter
@@ -196,9 +196,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
-- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Range by URL: <%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>
- Link to range by reference: [Range](<%= commit_range.to_reference %>)
-- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
+- Link to range by URL: [Range](<%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>)
#### CommitReferenceFilter
@@ -206,9 +206,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
-- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Commit by URL: <%= urls.project_commit_url(commit.project, commit) %>
- Link to commit by reference: [Commit](<%= commit.to_reference %>)
-- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
+- Link to commit by URL: [Commit](<%= urls.project_commit_url(commit.project, commit) %>)
#### LabelReferenceFilter
@@ -227,7 +227,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Milestone in another project: <%= xmilestone.to_reference(project) %>
- Ignored in code: `<%= simple_milestone.to_reference %>`
- Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link)
-- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>
+- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %>
- Link to milestone by URL: [Milestone](<%= milestone.to_reference %>)
### Task Lists
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 56daeffde27..e0cad1da86a 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -76,7 +76,7 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
- avatar_url = "#{gitlab_host}#{namespace_project_avatar_path(project.namespace, project)}"
+ avatar_url = "#{gitlab_host}#{project_avatar_path(project)}"
expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url))
end
end
@@ -292,7 +292,7 @@ describe ApplicationHelper do
let(:alternate_url) { 'http://company.example.com/getting-help' }
before do
- allow(current_application_settings).to receive(:help_page_support_url) { alternate_url }
+ stub_application_setting(help_page_support_url: alternate_url)
end
it 'returns the alternate support url' do
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 14847d0a49e..717ac1962d1 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -2,40 +2,34 @@ require 'spec_helper'
describe GitlabRoutingHelper do
describe 'Project URL helpers' do
- describe '#project_members_url' do
- let(:project) { build_stubbed(:empty_project) }
-
- it { expect(project_members_url(project)).to eq namespace_project_project_members_url(project.namespace, project) }
- end
-
describe '#project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(project_member_path(project_member)).to eq namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(project_member_path(project_member)).to eq project_project_member_path(project_member.source, project_member) }
end
describe '#request_access_project_members_path' do
let(:project) { build_stubbed(:empty_project) }
- it { expect(request_access_project_members_path(project)).to eq request_access_namespace_project_project_members_path(project.namespace, project) }
+ it { expect(request_access_project_members_path(project)).to eq request_access_project_project_members_path(project) }
end
describe '#leave_project_members_path' do
let(:project) { build_stubbed(:empty_project) }
- it { expect(leave_project_members_path(project)).to eq leave_namespace_project_project_members_path(project.namespace, project) }
+ it { expect(leave_project_members_path(project)).to eq leave_project_project_members_path(project) }
end
describe '#approve_access_request_project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_project_project_member_path(project_member.source, project_member) }
end
describe '#resend_invite_project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_project_project_member_path(project_member.source, project_member) }
end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 8da22dc78fa..e3f9d9db9eb 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -91,7 +91,7 @@ describe GroupsHelper do
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'outputs the groups in the correct order' do
- expect(group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
+ expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
end
end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 15cb620199d..d2e918ef014 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -77,54 +77,89 @@ describe IssuablesHelper do
}.with_indifferent_access
end
+ let(:issues_finder) { IssuesFinder.new(nil, params) }
+ let(:merge_requests_finder) { MergeRequestsFinder.new(nil, params) }
+
+ before do
+ allow(helper).to receive(:issues_finder).and_return(issues_finder)
+ allow(helper).to receive(:merge_requests_finder).and_return(merge_requests_finder)
+ end
+
it 'returns the cached value when called for the same issuable type & with the same params' do
- expect(helper).to receive(:params).twice.and_return(params)
- expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
- expect(helper).not_to receive(:issuables_count_for_state)
+ expect(issues_finder).not_to receive(:count_by_state)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
end
+ it 'takes confidential status into account when searching for issues' do
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+
+ expect(helper.issuables_state_counter_text(:issues, :opened))
+ .to include('42')
+
+ expect(issues_finder).to receive(:user_cannot_see_confidential_issues?).twice.and_return(false)
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 40)
+
+ expect(helper.issuables_state_counter_text(:issues, :opened))
+ .to include('40')
+
+ expect(issues_finder).to receive(:user_can_see_all_confidential_issues?).and_return(true)
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 45)
+
+ expect(helper.issuables_state_counter_text(:issues, :opened))
+ .to include('45')
+ end
+
+ it 'does not take confidential status into account when searching for merge requests' do
+ expect(merge_requests_finder).to receive(:count_by_state).and_return(opened: 42)
+ expect(merge_requests_finder).not_to receive(:user_cannot_see_confidential_issues?)
+ expect(merge_requests_finder).not_to receive(:user_can_see_all_confidential_issues?)
+
+ expect(helper.issuables_state_counter_text(:merge_requests, :opened))
+ .to include('42')
+ end
+
it 'does not take some keys into account in the cache key' do
- expect(helper).to receive(:params).and_return({
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+ expect(issues_finder).to receive(:params).and_return({
author_id: '11',
state: 'foo',
sort: 'foo',
utf8: 'foo',
page: 'foo'
}.with_indifferent_access)
- expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
- expect(helper).to receive(:params).and_return({
+ expect(issues_finder).not_to receive(:count_by_state)
+ expect(issues_finder).to receive(:params).and_return({
author_id: '11',
state: 'bar',
sort: 'bar',
utf8: 'bar',
page: 'bar'
}.with_indifferent_access)
- expect(helper).not_to receive(:issuables_count_for_state)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
end
it 'does not take params order into account in the cache key' do
- expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
- expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+ expect(issues_finder).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
+ expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
- expect(helper).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
- expect(helper).not_to receive(:issuables_count_for_state)
+ expect(issues_finder).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
+ expect(issues_finder).not_to receive(:count_by_state)
expect(helper.issuables_state_counter_text(:issues, :opened))
.to eq('<span>Open</span> <span class="badge">42</span>')
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 00db98fd9d2..8f7f17a484f 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -137,7 +137,7 @@ describe IssuesHelper do
let(:merge_request) { create(:merge_request) }
it "links just the merge request" do
- expected_path = namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ expected_path = project_merge_request_path(merge_request.project, merge_request)
expect(link_to_discussions_to_resolve(merge_request, nil)).to include(expected_path)
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index b4226f96a04..4b6a351cf70 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -25,17 +25,17 @@ describe MarkupHelper do
let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
it "links to the merge request" do
- expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
+ expected = project_merge_request_path(project, merge_request)
expect(helper.markdown(actual)).to match(expected)
end
it "links to the commit" do
- expected = namespace_project_commit_path(project.namespace, project, commit)
+ expected = project_commit_path(project, commit)
expect(helper.markdown(actual)).to match(expected)
end
it "links to the issue" do
- expected = namespace_project_issue_path(project.namespace, project, issue)
+ expected = project_issue_path(project, issue)
expect(helper.markdown(actual)).to match(expected)
end
end
@@ -46,7 +46,7 @@ describe MarkupHelper do
let(:second_issue) { create(:issue, project: second_project) }
it 'links to the issue' do
- expected = namespace_project_issue_path(second_project.namespace, second_project, second_issue)
+ expected = project_issue_path(second_project, second_issue)
expect(markdown(actual, project: second_project)).to match(expected)
end
end
@@ -69,7 +69,7 @@ describe MarkupHelper do
# First issue link
expect(doc.css('a')[1].attr('href'))
- .to eq namespace_project_issue_path(project.namespace, project, issues[0])
+ .to eq project_issue_path(project, issues[0])
expect(doc.css('a')[1].text).to eq issues[0].to_reference
# Internal commit link
@@ -78,7 +78,7 @@ describe MarkupHelper do
# Second issue link
expect(doc.css('a')[3].attr('href'))
- .to eq namespace_project_issue_path(project.namespace, project, issues[1])
+ .to eq project_issue_path(project, issues[1])
expect(doc.css('a')[3].text).to eq issues[1].to_reference
# Trailing commit link
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 3cb809d42b5..b8f9c02a486 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -1,6 +1,42 @@
require 'spec_helper'
describe MilestonesHelper do
+ describe '#milestones_filter_dropdown_path' do
+ let(:project) { create(:empty_project) }
+ let(:project2) { create(:empty_project) }
+ let(:group) { create(:group) }
+
+ context 'when @project present' do
+ it 'returns project milestones JSON URL' do
+ assign(:project, project)
+
+ expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project, :json))
+ end
+ end
+
+ context 'when @target_project present' do
+ it 'returns targeted project milestones JSON URL' do
+ assign(:target_project, project2)
+
+ expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project2, :json))
+ end
+ end
+
+ context 'when @group present' do
+ it 'returns group milestones JSON URL' do
+ assign(:group, group)
+
+ expect(helper.milestones_filter_dropdown_path).to eq(group_milestones_path(group, :json))
+ end
+ end
+
+ context 'when neither of @project/@target_project/@group present' do
+ it 'returns dashboard milestones JSON URL' do
+ expect(helper.milestones_filter_dropdown_path).to eq(dashboard_milestones_path(:json))
+ end
+ end
+ end
+
describe "#milestone_date_range" do
def result_for(*args)
milestone_date_range(build(:milestone, *args))
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index cc861af8533..56f252ba273 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -53,7 +53,7 @@ describe NotesHelper do
let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
it 'returns the diff path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
end
end
@@ -77,7 +77,7 @@ describe NotesHelper do
end
it 'returns the diff version path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
end
end
@@ -101,7 +101,7 @@ describe NotesHelper do
end
it 'returns the diff version comparison path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
end
end
@@ -129,7 +129,7 @@ describe NotesHelper do
end
it 'returns the diff path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
end
end
@@ -160,7 +160,7 @@ describe NotesHelper do
let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
end
end
@@ -168,7 +168,7 @@ describe NotesHelper do
let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
end
end
@@ -176,7 +176,7 @@ describe NotesHelper do
let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion }
it 'returns the commit path' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit))
end
end
end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index cb727430117..9e561d0f191 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -170,6 +170,11 @@ describe SubmoduleHelper do
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
end
+ it 'with trailing whitespace' do
+ result = relative_self_links('../test.git ', commit_id)
+ expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
+ end
+
it 'two levels down' do
result = relative_self_links('../../test.git', commit_id)
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb
index a507d7f7f2b..d4189f902fd 100644
--- a/spec/initializers/8_metrics_spec.rb
+++ b/spec/initializers/8_metrics_spec.rb
@@ -1,17 +1,25 @@
require 'spec_helper'
-require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
+ let(:unicorn_sampler) { double(:unicorn_sampler) }
+ let(:influx_sampler) { double(:influx_sampler) }
+
before do
allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_method)
allow(config).to receive(:instrument_instance_methods)
+ allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
+ allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
+ allow(unicorn_sampler).to receive(:start)
+ allow(influx_sampler).to receive(:start)
+ allow(Gitlab::Application).to receive(:configure)
end
it 'can autoload and instrument all files' do
+ require_relative '../../config/initializers/8_metrics'
expect { instrument_classes(config) }.not_to raise_error
end
end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 3fc03324d16..8e056882108 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
import Cookies from 'js-cookie';
-import AwardsHandler from '~/awards_handler';
+import loadAwardsHandler from '~/awards_handler';
import '~/lib/utils/common_utils';
@@ -26,14 +26,13 @@ import '~/lib/utils/common_utils';
describe('AwardsHandler', function() {
preloadFixtures('issues/issue_with_comment.html.raw');
- beforeEach(function() {
+ beforeEach(function(done) {
loadFixtures('issues/issue_with_comment.html.raw');
- awardsHandler = new AwardsHandler;
- spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
- return function(button, url, emoji, cb) {
- return cb();
- };
- })(this));
+ loadAwardsHandler(true).then((obj) => {
+ awardsHandler = obj;
+ spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+ done();
+ }).catch(fail);
let isEmojiMenuBuilt = false;
openAndWaitForEmojiMenu = function() {
diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
index 1ed96a67478..ec2c549e032 100644
--- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
+++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
@@ -1,4 +1,4 @@
-import { getUnicodeSupportMap } from '~/behaviors/gl_emoji/unicode_support_map';
+import getUnicodeSupportMap from '~/emoji/support/unicode_support_map';
import AccessorUtilities from '~/lib/utils/accessor';
describe('Unicode Support Map', () => {
diff --git a/spec/javascripts/close_reopen_report_toggle_spec.js b/spec/javascripts/close_reopen_report_toggle_spec.js
new file mode 100644
index 00000000000..925e959c85a
--- /dev/null
+++ b/spec/javascripts/close_reopen_report_toggle_spec.js
@@ -0,0 +1,270 @@
+import CloseReopenReportToggle from '~/close_reopen_report_toggle';
+import DropLab from '~/droplab/drop_lab';
+
+describe('CloseReopenReportToggle', () => {
+ describe('class constructor', () => {
+ const dropdownTrigger = {};
+ const dropdownList = {};
+ const button = {};
+ let commentTypeToggle;
+
+ beforeEach(function () {
+ commentTypeToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+ });
+
+ it('sets .dropdownTrigger', function () {
+ expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger);
+ });
+
+ it('sets .dropdownList', function () {
+ expect(commentTypeToggle.dropdownList).toBe(dropdownList);
+ });
+
+ it('sets .button', function () {
+ expect(commentTypeToggle.button).toBe(button);
+ });
+ });
+
+ describe('initDroplab', () => {
+ let closeReopenReportToggle;
+ const dropdownList = jasmine.createSpyObj('dropdownList', ['querySelector']);
+ const dropdownTrigger = {};
+ const button = {};
+ const reopenItem = {};
+ const closeItem = {};
+ const config = {};
+
+ beforeEach(() => {
+ spyOn(DropLab.prototype, 'init');
+ dropdownList.querySelector.and.returnValues(reopenItem, closeItem);
+
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'setConfig').and.returnValue(config);
+
+ closeReopenReportToggle.initDroplab();
+ });
+
+ it('sets .reopenItem and .closeItem', () => {
+ expect(dropdownList.querySelector).toHaveBeenCalledWith('.reopen-item');
+ expect(dropdownList.querySelector).toHaveBeenCalledWith('.close-item');
+ expect(closeReopenReportToggle.reopenItem).toBe(reopenItem);
+ expect(closeReopenReportToggle.closeItem).toBe(closeItem);
+ });
+
+ it('sets .droplab', () => {
+ expect(closeReopenReportToggle.droplab).toEqual(jasmine.any(Object));
+ });
+
+ it('calls .setConfig', () => {
+ expect(closeReopenReportToggle.setConfig).toHaveBeenCalled();
+ });
+
+ it('calls droplab.init', () => {
+ expect(DropLab.prototype.init).toHaveBeenCalledWith(
+ dropdownTrigger,
+ dropdownList,
+ jasmine.any(Array),
+ config,
+ );
+ });
+ });
+
+ describe('updateButton', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = jasmine.createSpyObj('button', ['blur']);
+ const isClosed = true;
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'toggleButtonType');
+
+ closeReopenReportToggle.updateButton(isClosed);
+ });
+
+ it('calls .toggleButtonType', () => {
+ expect(closeReopenReportToggle.toggleButtonType).toHaveBeenCalledWith(isClosed);
+ });
+
+ it('calls .button.blur', () => {
+ expect(closeReopenReportToggle.button.blur).toHaveBeenCalled();
+ });
+ });
+
+ describe('toggleButtonType', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ const isClosed = true;
+ const showItem = jasmine.createSpyObj('showItem', ['click']);
+ const hideItem = {};
+ showItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
+ hideItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'getButtonTypes').and.returnValue([showItem, hideItem]);
+
+ closeReopenReportToggle.toggleButtonType(isClosed);
+ });
+
+ it('calls .getButtonTypes', () => {
+ expect(closeReopenReportToggle.getButtonTypes).toHaveBeenCalledWith(isClosed);
+ });
+
+ it('removes hide class and add selected class to showItem, opposite for hideItem', () => {
+ expect(showItem.classList.remove).toHaveBeenCalledWith('hidden');
+ expect(showItem.classList.add).toHaveBeenCalledWith('droplab-item-selected');
+ expect(hideItem.classList.add).toHaveBeenCalledWith('hidden');
+ expect(hideItem.classList.remove).toHaveBeenCalledWith('droplab-item-selected');
+ });
+
+ it('clicks the showItem', () => {
+ expect(showItem.click).toHaveBeenCalled();
+ });
+ });
+
+ describe('getButtonTypes', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ const reopenItem = {};
+ const closeItem = {};
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ closeReopenReportToggle.reopenItem = reopenItem;
+ closeReopenReportToggle.closeItem = closeItem;
+ });
+
+ it('returns reopenItem, closeItem if isClosed is true', () => {
+ const buttonTypes = closeReopenReportToggle.getButtonTypes(true);
+
+ expect(buttonTypes).toEqual([reopenItem, closeItem]);
+ });
+
+ it('returns closeItem, reopenItem if isClosed is false', () => {
+ const buttonTypes = closeReopenReportToggle.getButtonTypes(false);
+
+ expect(buttonTypes).toEqual([closeItem, reopenItem]);
+ });
+ });
+
+ describe('setDisable', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
+ const button = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+ });
+
+ it('disable .button and .dropdownTrigger if shouldDisable is true', () => {
+ closeReopenReportToggle.setDisable(true);
+
+ expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ });
+
+ it('disable .button and .dropdownTrigger if shouldDisable is undefined', () => {
+ closeReopenReportToggle.setDisable();
+
+ expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ });
+
+ it('enable .button and .dropdownTrigger if shouldDisable is false', () => {
+ closeReopenReportToggle.setDisable(false);
+
+ expect(button.removeAttribute).toHaveBeenCalledWith('disabled');
+ expect(dropdownTrigger.removeAttribute).toHaveBeenCalledWith('disabled');
+ });
+ });
+
+ describe('setConfig', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ let config;
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ config = closeReopenReportToggle.setConfig();
+ });
+
+ it('returns a config object', () => {
+ expect(config).toEqual({
+ InputSetter: [
+ {
+ input: button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'data-value',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'title',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-button-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: dropdownTrigger,
+ valueAttribute: 'data-toggle-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-url',
+ inputAttribute: 'href',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-method',
+ inputAttribute: 'data-method',
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/emoji_spec.js
index a09e0072fa8..fa11c602ec3 100644
--- a/spec/javascripts/gl_emoji_spec.js
+++ b/spec/javascripts/emoji_spec.js
@@ -1,12 +1,11 @@
-import { glEmojiTag } from '~/behaviors/gl_emoji';
-import {
- isEmojiUnicodeSupported,
+import { glEmojiTag } from '~/emoji';
+import isEmojiUnicodeSupported, {
isFlagEmoji,
isKeycapEmoji,
isSkinToneComboEmoji,
isHorceRacingSkinToneComboEmoji,
isPersonZwjEmoji,
-} from '~/behaviors/gl_emoji/is_emoji_unicode_supported';
+} from '~/emoji/support/is_emoji_unicode_supported';
const emptySupportMap = {
personZwj: false,
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 6e855530b21..f2c6ec24dd7 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -86,6 +86,16 @@ describe('Store', () => {
store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(false);
});
+
+ it('should keep folder open when environments are updated', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+ });
});
describe('setfolderContent', () => {
@@ -97,6 +107,17 @@ describe('Store', () => {
expect(store.state.environments[1].children.length).toEqual(serverData.length);
expect(store.state.environments[1].children[0].isChildren).toEqual(true);
});
+
+ it('should keep folder content when environments are updated', () => {
+ store.storeEnvironments(serverData);
+
+ store.setfolderContent(store.state.environments[1], serverData);
+
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ // poll
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ });
});
describe('store pagination', () => {
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index f7708301b6e..0132f4b7c93 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -66,4 +66,38 @@ describe('Dropdown User', () => {
window.gon = {};
});
});
+
+ describe('hideCurrentUser', () => {
+ const fixtureTemplate = 'issues/issue_list.html.raw';
+ preloadFixtures(fixtureTemplate);
+
+ let dropdown;
+ let authorFilterDropdownElement;
+
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
+ const dummyInput = document.createElement('div');
+ dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput);
+ });
+
+ const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user');
+
+ it('hides the current user from dropdown', () => {
+ const currentUserElement = findCurrentUserElement();
+ expect(currentUserElement).not.toBe(null);
+
+ dropdown.hideCurrentUser();
+
+ expect(currentUserElement.classList).toContain('hidden');
+ });
+
+ it('does nothing if no user is logged in', () => {
+ const currentUserElement = findCurrentUserElement();
+ currentUserElement.parentNode.removeChild(currentUserElement);
+ expect(findCurrentUserElement()).toBe(null);
+
+ dropdown.hideCurrentUser();
+ });
+ });
});
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index daaddd8f390..7e2f364ffa4 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -55,27 +55,14 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merge_request)
end
- it 'merge_requests/inline_changes_tab_with_comments.json' do |example|
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(example.description, merge_request, action: :diffs, format: :json)
- end
-
- it 'merge_requests/parallel_changes_tab_with_comments.json' do |example|
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(example.description, merge_request, action: :diffs, format: :json, view: 'parallel')
- end
-
private
- def render_merge_request(fixture_file_name, merge_request, action: :show, format: :html, view: 'inline')
- get action,
+ def render_merge_request(fixture_file_name, merge_request)
+ get :show,
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.to_param,
- format: format,
- view: view
+ format: :html
expect(response).to be_success
store_frontend_fixture(response, fixture_file_name)
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
new file mode 100644
index 00000000000..ac5b06ace6d
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -0,0 +1,57 @@
+
+require 'spec_helper'
+
+describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+ let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:path) { "files/ruby/popen.rb" }
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ )
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_request_diffs/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example|
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(example.description, merge_request)
+ end
+
+ it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do |example|
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(example.description, merge_request, view: 'parallel')
+ end
+
+ private
+
+ def render_merge_request(fixture_file_name, merge_request, view: 'inline')
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.to_param,
+ format: :json,
+ view: view
+
+ expect(response).to be_success
+ store_frontend_fixture(response, fixture_file_name)
+ end
+end
diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml
new file mode 100644
index 00000000000..7886e995e57
--- /dev/null
+++ b/spec/javascripts/fixtures/oauth_remember_me.html.haml
@@ -0,0 +1,5 @@
+#oauth-container
+ %input#remember_me{ type: "checkbox" }
+
+ %a.oauth-login.twitter{ href: "http://example.com/" }
+ %a.oauth-login.github{ href: "http://example.com/" }
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 9df92318864..bc13373a27e 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -42,9 +42,6 @@ describe('Issuable output', () => {
}).$mount();
});
- afterEach(() => {
- });
-
it('should render a title/description/edited and update title/description/edited on update', (done) => {
vm.poll.options.successCallback({
json() {
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index f3fdbff01a6..360691a3546 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -44,32 +44,34 @@ describe('Description component', () => {
});
});
- it('re-inits the TaskList when description changed', (done) => {
- spyOn(gl, 'TaskList');
- vm.descriptionHtml = 'changed';
-
- setTimeout(() => {
- expect(
- gl.TaskList,
- ).toHaveBeenCalled();
-
- done();
- });
- });
-
- it('does not re-init the TaskList when canUpdate is false', (done) => {
- spyOn(gl, 'TaskList');
- vm.canUpdate = false;
- vm.descriptionHtml = 'changed';
-
- setTimeout(() => {
- expect(
- gl.TaskList,
- ).not.toHaveBeenCalled();
-
- done();
- });
- });
+ // TODO: gl.TaskList no longer exists. rewrite these tests once we have a way to rewire ES modules
+
+ // it('re-inits the TaskList when description changed', (done) => {
+ // spyOn(gl, 'TaskList');
+ // vm.descriptionHtml = 'changed';
+ //
+ // setTimeout(() => {
+ // expect(
+ // gl.TaskList,
+ // ).toHaveBeenCalled();
+ //
+ // done();
+ // });
+ // });
+
+ // it('does not re-init the TaskList when canUpdate is false', (done) => {
+ // spyOn(gl, 'TaskList');
+ // vm.canUpdate = false;
+ // vm.descriptionHtml = 'changed';
+ //
+ // setTimeout(() => {
+ // expect(
+ // gl.TaskList,
+ // ).not.toHaveBeenCalled();
+ //
+ // done();
+ // });
+ // });
describe('taskStatus', () => {
it('adds full taskStatus', (done) => {
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index df97a100b0d..0c8c4d2cea6 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,10 +1,10 @@
/* 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() {
- let $boxClosed, $boxOpen, $btnClose, $btnReopen;
+ let $boxClosed, $boxOpen, $btn;
preloadFixtures('issues/closed-issue.html.raw');
preloadFixtures('issues/issue-with-task-list.html.raw');
@@ -20,9 +20,7 @@ describe('Issue', function() {
function expectIssueState(isIssueOpen) {
expectVisibility($boxClosed, !isIssueOpen);
expectVisibility($boxOpen, isIssueOpen);
-
- expectVisibility($btnClose, isIssueOpen);
- expectVisibility($btnReopen, !isIssueOpen);
+ expect($btn).toHaveText(isIssueOpen ? 'Close issue' : 'Reopen issue');
}
function expectNewBranchButtonState(isPending, canCreate) {
@@ -57,7 +55,7 @@ describe('Issue', function() {
}
}
- function findElements() {
+ function findElements(isIssueInitiallyOpen) {
$boxClosed = $('div.status-box-closed');
expect($boxClosed).toExist();
expect($boxClosed).toHaveText('Closed');
@@ -66,13 +64,9 @@ describe('Issue', function() {
expect($boxOpen).toExist();
expect($boxOpen).toHaveText('Open');
- $btnClose = $('.btn-close.btn-grouped');
- expect($btnClose).toExist();
- expect($btnClose).toHaveText('Close issue');
-
- $btnReopen = $('.btn-reopen.btn-grouped');
- expect($btnReopen).toExist();
- expect($btnReopen).toHaveText('Reopen issue');
+ $btn = $('.js-issuable-close-button');
+ expect($btn).toExist();
+ expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue');
}
describe('task lists', function() {
@@ -99,7 +93,6 @@ describe('Issue', function() {
function ajaxSpy(req) {
if (req.url === this.$triggeredButton.attr('href')) {
expect(req.type).toBe('PUT');
- expect(this.$triggeredButton).toHaveProp('disabled', true);
expectNewBranchButtonState(true, false);
return this.issueStateDeferred;
} else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) {
@@ -119,10 +112,11 @@ describe('Issue', function() {
loadFixtures('issues/closed-issue.html.raw');
}
- findElements();
+ findElements(isIssueInitiallyOpen);
this.issue = new Issue();
expectIssueState(isIssueInitiallyOpen);
- this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
+
+ this.$triggeredButton = $btn;
this.$projectIssuesCounter = $('.issue_counter');
this.$projectIssuesCounter.text('1,001');
@@ -143,7 +137,7 @@ describe('Issue', function() {
});
expectIssueState(!isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
expectNewBranchButtonState(false, !isIssueInitiallyOpen);
});
@@ -158,7 +152,7 @@ describe('Issue', function() {
});
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
@@ -172,7 +166,7 @@ describe('Issue', function() {
});
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
@@ -195,4 +189,37 @@ 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/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 52cf217c25f..55037bbbf73 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -143,6 +143,7 @@ import '~/lib/utils/common_utils';
it('should return valid parameter', () => {
const value = gl.utils.getParameterByName('scope');
+ expect(gl.utils.getParameterByName('p')).toEqual('2');
expect(value).toBe('all');
});
diff --git a/spec/javascripts/lib/utils/dom_utils_spec.js b/spec/javascripts/lib/utils/dom_utils_spec.js
new file mode 100644
index 00000000000..867bf5912d1
--- /dev/null
+++ b/spec/javascripts/lib/utils/dom_utils_spec.js
@@ -0,0 +1,35 @@
+import { addClassIfElementExists } from '~/lib/utils/dom_utils';
+
+describe('DOM Utils', () => {
+ describe('addClassIfElementExists', () => {
+ const className = 'biology';
+ const fixture = `
+ <div class="parent">
+ <div class="child"></div>
+ </div>
+ `;
+
+ let parentElement;
+
+ beforeEach(() => {
+ setFixtures(fixture);
+ parentElement = document.querySelector('.parent');
+ });
+
+ it('adds class if element exists', () => {
+ const childElement = parentElement.querySelector('.child');
+ expect(childElement).not.toBe(null);
+
+ addClassIfElementExists(childElement, className);
+
+ expect(childElement.classList).toContain(className);
+ });
+
+ it('does not throw if element does not exist', () => {
+ const childElement = parentElement.querySelector('.other-child');
+ expect(childElement).toBe(null);
+
+ addClassIfElementExists(childElement, className);
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index 9e9eb17d439..395dc560671 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -15,7 +15,7 @@ describe('Merge request notes', () => {
gl.utils = gl.utils || {};
const discussionTabFixture = 'merge_requests/diff_comment.html.raw';
- const changesTabJsonFixture = 'merge_requests/inline_changes_tab_with_comments.json';
+ const changesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
preloadFixtures(discussionTabFixture, changesTabJsonFixture);
describe('Discussion tab with diff comments', () => {
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index f444bcaf847..6ff42e2378d 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -2,10 +2,12 @@
/* global MergeRequest */
import '~/merge_request';
+import CloseReopenReportToggle from '~/close_reopen_report_toggle';
+import IssuablesHelper from '~/helpers/issuables_helper';
(function() {
describe('MergeRequest', function() {
- return describe('task lists', function() {
+ describe('task lists', function() {
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
@@ -27,5 +29,34 @@ import '~/merge_request';
return $('.js-task-list-field').trigger('tasklist:changed');
});
});
+
+ describe('class constructor', () => {
+ it('calls .initCloseReopenReport', () => {
+ spyOn(IssuablesHelper, 'initCloseReopenReport');
+
+ new MergeRequest(); // eslint-disable-line no-new
+
+ expect(IssuablesHelper.initCloseReopenReport).toHaveBeenCalled();
+ });
+
+ it('calls .initDroplab', () => {
+ const container = jasmine.createSpyObj('container', ['querySelector']);
+ const dropdownTrigger = {};
+ const dropdownList = {};
+ const button = {};
+
+ spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
+ spyOn(document, 'querySelector').and.returnValue(container);
+ container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
+
+ new MergeRequest(); // eslint-disable-line no-new
+
+ 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();
+ });
+ });
});
}).call(window);
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index bb6b5d852d3..dc40244c20e 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -6,7 +6,6 @@ import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
import '~/diff';
-import '~/single_file_diff';
import '~/files_comment_button';
import '~/notes';
import 'vendor/jquery.scrollTo';
@@ -23,8 +22,8 @@ import 'vendor/jquery.scrollTo';
$.extend(stubLocation, defaults, stubs || {});
};
- const inlineChangesTabJsonFixture = 'merge_requests/inline_changes_tab_with_comments.json';
- const parallelChangesTabJsonFixture = 'merge_requests/parallel_changes_tab_with_comments.json';
+ const inlineChangesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
+ const parallelChangesTabJsonFixture = 'merge_request_diffs/parallel_changes_tab_with_comments.json';
preloadFixtures(
'merge_requests/merge_request_with_task_list.html.raw',
'merge_requests/diff_comment.html.raw',
@@ -52,14 +51,10 @@ import 'vendor/jquery.scrollTo';
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
- it('shows the first tab when action is show', function () {
+ it('shows the notes tab when action is show', function () {
this.subject('show');
expect($('#notes')).toHaveClass('active');
});
- it('shows the notes tab when action is notes', function () {
- this.subject('notes');
- expect($('#notes')).toHaveClass('active');
- });
it('shows the commits tab when action is commits', function () {
this.subject('commits');
expect($('#commits')).toHaveClass('active');
@@ -161,7 +156,7 @@ import 'vendor/jquery.scrollTo';
setLocation({
pathname: '/foo/bar/merge_requests/1/commits'
});
- expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
});
@@ -170,7 +165,7 @@ import 'vendor/jquery.scrollTo';
pathname: '/foo/bar/merge_requests/1/diffs'
});
- expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
});
@@ -178,7 +173,7 @@ import 'vendor/jquery.scrollTo';
setLocation({
pathname: '/foo/bar/merge_requests/1/diffs.html'
});
- expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
});
diff --git a/spec/javascripts/monitoring/deployments_spec.js b/spec/javascripts/monitoring/deployments_spec.js
deleted file mode 100644
index 19bc11d0f24..00000000000
--- a/spec/javascripts/monitoring/deployments_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import d3 from 'd3';
-import PrometheusGraph from '~/monitoring/prometheus_graph';
-import Deployments from '~/monitoring/deployments';
-import { prometheusMockData } from './prometheus_mock_data';
-
-describe('Metrics deployments', () => {
- const fixtureName = 'environments/metrics/metrics.html.raw';
- let deployment;
- let prometheusGraph;
-
- const graphElement = () => document.querySelector('.prometheus-graph');
-
- preloadFixtures(fixtureName);
-
- beforeEach((done) => {
- // Setup the view
- loadFixtures(fixtureName);
-
- d3.selectAll('.prometheus-graph')
- .append('g')
- .attr('class', 'graph-container');
-
- prometheusGraph = new PrometheusGraph();
- deployment = new Deployments(1000, 500);
-
- spyOn(prometheusGraph, 'init');
- spyOn($, 'ajax').and.callFake(() => {
- const d = $.Deferred();
- d.resolve({
- deployments: [{
- id: 1,
- created_at: deployment.chartData[10].time,
- sha: 'testing',
- tag: false,
- ref: {
- name: 'testing',
- },
- }, {
- id: 2,
- created_at: deployment.chartData[15].time,
- sha: '',
- tag: true,
- ref: {
- name: 'tag',
- },
- }],
- });
-
- setTimeout(done);
-
- return d.promise();
- });
-
- prometheusGraph.configureGraph();
- prometheusGraph.transformData(prometheusMockData.metrics);
-
- deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
- });
-
- it('creates line on graph for deploment', () => {
- expect(
- graphElement().querySelectorAll('.deployment-line').length,
- ).toBe(2);
- });
-
- it('creates hidden deploy boxes', () => {
- expect(
- graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
- ).toBe(2);
- });
-
- it('hides the info boxes by default', () => {
- expect(
- graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
- ).toBe(2);
- });
-
- it('shows sha short code when tag is false', () => {
- expect(
- graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
- ).toContain('testin');
- });
-
- it('shows ref name when tag is true', () => {
- expect(
- graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
- ).toContain('tag');
- });
-
- it('shows info box when moving mouse over line', () => {
- deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');
-
- expect(
- graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
- ).toBe(1);
-
- expect(
- graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
- ).toBeNull();
- });
-
- it('hides previously visible info box when moving mouse away', () => {
- deployment.mouseOverDeployInfo(500, 'cpu_values');
-
- expect(
- graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
- ).toBe(2);
-
- expect(
- graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
- ).not.toBeNull();
- });
-
- describe('refText', () => {
- it('returns shortened SHA', () => {
- expect(
- Deployments.refText({
- tag: false,
- sha: '123456789',
- }),
- ).toBe('123456');
- });
-
- it('returns tag name', () => {
- expect(
- Deployments.refText({
- tag: true,
- ref: 'v1.0',
- }),
- ).toBe('v1.0');
- });
- });
-});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
new file mode 100644
index 00000000000..b69f4eddffc
--- /dev/null
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -0,0 +1,4230 @@
+/* eslint-disable quote-props, indent, comma-dangle */
+
+const metricsGroupsAPIResponse = {
+ 'success': true,
+ 'data': [
+ {
+ 'group': 'Kubernetes',
+ 'priority': 1,
+ 'metrics': [
+ {
+ 'title': 'Memory usage',
+ 'weight': 1,
+ 'queries': [
+ {
+ 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
+ 'y_label': 'Memory',
+ 'unit': 'MiB',
+ 'result': [
+ {
+ 'metric': {},
+ 'values': [
+ [
+ 1495700554.925,
+ '8.0390625'
+ ],
+ [
+ 1495700614.925,
+ '8.0390625'
+ ],
+ [
+ 1495700674.925,
+ '8.0390625'
+ ],
+ [
+ 1495700734.925,
+ '8.0390625'
+ ],
+ [
+ 1495700794.925,
+ '8.0390625'
+ ],
+ [
+ 1495700854.925,
+ '8.0390625'
+ ],
+ [
+ 1495700914.925,
+ '8.0390625'
+ ],
+ [
+ 1495700974.925,
+ '8.0390625'
+ ],
+ [
+ 1495701034.925,
+ '8.0390625'
+ ],
+ [
+ 1495701094.925,
+ '8.0390625'
+ ],
+ [
+ 1495701154.925,
+ '8.0390625'
+ ],
+ [
+ 1495701214.925,
+ '8.0390625'
+ ],
+ [
+ 1495701274.925,
+ '8.0390625'
+ ],
+ [
+ 1495701334.925,
+ '8.0390625'
+ ],
+ [
+ 1495701394.925,
+ '8.0390625'
+ ],
+ [
+ 1495701454.925,
+ '8.0390625'
+ ],
+ [
+ 1495701514.925,
+ '8.0390625'
+ ],
+ [
+ 1495701574.925,
+ '8.0390625'
+ ],
+ [
+ 1495701634.925,
+ '8.0390625'
+ ],
+ [
+ 1495701694.925,
+ '8.0390625'
+ ],
+ [
+ 1495701754.925,
+ '8.0390625'
+ ],
+ [
+ 1495701814.925,
+ '8.0390625'
+ ],
+ [
+ 1495701874.925,
+ '8.0390625'
+ ],
+ [
+ 1495701934.925,
+ '8.0390625'
+ ],
+ [
+ 1495701994.925,
+ '8.0390625'
+ ],
+ [
+ 1495702054.925,
+ '8.0390625'
+ ],
+ [
+ 1495702114.925,
+ '8.0390625'
+ ],
+ [
+ 1495702174.925,
+ '8.0390625'
+ ],
+ [
+ 1495702234.925,
+ '8.0390625'
+ ],
+ [
+ 1495702294.925,
+ '8.0390625'
+ ],
+ [
+ 1495702354.925,
+ '8.0390625'
+ ],
+ [
+ 1495702414.925,
+ '8.0390625'
+ ],
+ [
+ 1495702474.925,
+ '8.0390625'
+ ],
+ [
+ 1495702534.925,
+ '8.0390625'
+ ],
+ [
+ 1495702594.925,
+ '8.0390625'
+ ],
+ [
+ 1495702654.925,
+ '8.0390625'
+ ],
+ [
+ 1495702714.925,
+ '8.0390625'
+ ],
+ [
+ 1495702774.925,
+ '8.0390625'
+ ],
+ [
+ 1495702834.925,
+ '8.0390625'
+ ],
+ [
+ 1495702894.925,
+ '8.0390625'
+ ],
+ [
+ 1495702954.925,
+ '8.0390625'
+ ],
+ [
+ 1495703014.925,
+ '8.0390625'
+ ],
+ [
+ 1495703074.925,
+ '8.0390625'
+ ],
+ [
+ 1495703134.925,
+ '8.0390625'
+ ],
+ [
+ 1495703194.925,
+ '8.0390625'
+ ],
+ [
+ 1495703254.925,
+ '8.03515625'
+ ],
+ [
+ 1495703314.925,
+ '8.03515625'
+ ],
+ [
+ 1495703374.925,
+ '8.03515625'
+ ],
+ [
+ 1495703434.925,
+ '8.03515625'
+ ],
+ [
+ 1495703494.925,
+ '8.03515625'
+ ],
+ [
+ 1495703554.925,
+ '8.03515625'
+ ],
+ [
+ 1495703614.925,
+ '8.03515625'
+ ],
+ [
+ 1495703674.925,
+ '8.03515625'
+ ],
+ [
+ 1495703734.925,
+ '8.03515625'
+ ],
+ [
+ 1495703794.925,
+ '8.03515625'
+ ],
+ [
+ 1495703854.925,
+ '8.03515625'
+ ],
+ [
+ 1495703914.925,
+ '8.03515625'
+ ],
+ [
+ 1495703974.925,
+ '8.03515625'
+ ],
+ [
+ 1495704034.925,
+ '8.03515625'
+ ],
+ [
+ 1495704094.925,
+ '8.03515625'
+ ],
+ [
+ 1495704154.925,
+ '8.03515625'
+ ],
+ [
+ 1495704214.925,
+ '7.9296875'
+ ],
+ [
+ 1495704274.925,
+ '7.9296875'
+ ],
+ [
+ 1495704334.925,
+ '7.9296875'
+ ],
+ [
+ 1495704394.925,
+ '7.9296875'
+ ],
+ [
+ 1495704454.925,
+ '7.9296875'
+ ],
+ [
+ 1495704514.925,
+ '7.9296875'
+ ],
+ [
+ 1495704574.925,
+ '7.9296875'
+ ],
+ [
+ 1495704634.925,
+ '7.9296875'
+ ],
+ [
+ 1495704694.925,
+ '7.9296875'
+ ],
+ [
+ 1495704754.925,
+ '7.9296875'
+ ],
+ [
+ 1495704814.925,
+ '7.9296875'
+ ],
+ [
+ 1495704874.925,
+ '7.9296875'
+ ],
+ [
+ 1495704934.925,
+ '7.9296875'
+ ],
+ [
+ 1495704994.925,
+ '7.9296875'
+ ],
+ [
+ 1495705054.925,
+ '7.9296875'
+ ],
+ [
+ 1495705114.925,
+ '7.9296875'
+ ],
+ [
+ 1495705174.925,
+ '7.9296875'
+ ],
+ [
+ 1495705234.925,
+ '7.9296875'
+ ],
+ [
+ 1495705294.925,
+ '7.9296875'
+ ],
+ [
+ 1495705354.925,
+ '7.9296875'
+ ],
+ [
+ 1495705414.925,
+ '7.9296875'
+ ],
+ [
+ 1495705474.925,
+ '7.9296875'
+ ],
+ [
+ 1495705534.925,
+ '7.9296875'
+ ],
+ [
+ 1495705594.925,
+ '7.9296875'
+ ],
+ [
+ 1495705654.925,
+ '7.9296875'
+ ],
+ [
+ 1495705714.925,
+ '7.9296875'
+ ],
+ [
+ 1495705774.925,
+ '7.9296875'
+ ],
+ [
+ 1495705834.925,
+ '7.9296875'
+ ],
+ [
+ 1495705894.925,
+ '7.9296875'
+ ],
+ [
+ 1495705954.925,
+ '7.9296875'
+ ],
+ [
+ 1495706014.925,
+ '7.9296875'
+ ],
+ [
+ 1495706074.925,
+ '7.9296875'
+ ],
+ [
+ 1495706134.925,
+ '7.9296875'
+ ],
+ [
+ 1495706194.925,
+ '7.9296875'
+ ],
+ [
+ 1495706254.925,
+ '7.9296875'
+ ],
+ [
+ 1495706314.925,
+ '7.9296875'
+ ],
+ [
+ 1495706374.925,
+ '7.9296875'
+ ],
+ [
+ 1495706434.925,
+ '7.9296875'
+ ],
+ [
+ 1495706494.925,
+ '7.9296875'
+ ],
+ [
+ 1495706554.925,
+ '7.9296875'
+ ],
+ [
+ 1495706614.925,
+ '7.9296875'
+ ],
+ [
+ 1495706674.925,
+ '7.9296875'
+ ],
+ [
+ 1495706734.925,
+ '7.9296875'
+ ],
+ [
+ 1495706794.925,
+ '7.9296875'
+ ],
+ [
+ 1495706854.925,
+ '7.9296875'
+ ],
+ [
+ 1495706914.925,
+ '7.9296875'
+ ],
+ [
+ 1495706974.925,
+ '7.9296875'
+ ],
+ [
+ 1495707034.925,
+ '7.9296875'
+ ],
+ [
+ 1495707094.925,
+ '7.9296875'
+ ],
+ [
+ 1495707154.925,
+ '7.9296875'
+ ],
+ [
+ 1495707214.925,
+ '7.9296875'
+ ],
+ [
+ 1495707274.925,
+ '7.9296875'
+ ],
+ [
+ 1495707334.925,
+ '7.9296875'
+ ],
+ [
+ 1495707394.925,
+ '7.9296875'
+ ],
+ [
+ 1495707454.925,
+ '7.9296875'
+ ],
+ [
+ 1495707514.925,
+ '7.9296875'
+ ],
+ [
+ 1495707574.925,
+ '7.9296875'
+ ],
+ [
+ 1495707634.925,
+ '7.9296875'
+ ],
+ [
+ 1495707694.925,
+ '7.9296875'
+ ],
+ [
+ 1495707754.925,
+ '7.9296875'
+ ],
+ [
+ 1495707814.925,
+ '7.9296875'
+ ],
+ [
+ 1495707874.925,
+ '7.9296875'
+ ],
+ [
+ 1495707934.925,
+ '7.9296875'
+ ],
+ [
+ 1495707994.925,
+ '7.9296875'
+ ],
+ [
+ 1495708054.925,
+ '7.9296875'
+ ],
+ [
+ 1495708114.925,
+ '7.9296875'
+ ],
+ [
+ 1495708174.925,
+ '7.9296875'
+ ],
+ [
+ 1495708234.925,
+ '7.9296875'
+ ],
+ [
+ 1495708294.925,
+ '7.9296875'
+ ],
+ [
+ 1495708354.925,
+ '7.9296875'
+ ],
+ [
+ 1495708414.925,
+ '7.9296875'
+ ],
+ [
+ 1495708474.925,
+ '7.9296875'
+ ],
+ [
+ 1495708534.925,
+ '7.9296875'
+ ],
+ [
+ 1495708594.925,
+ '7.9296875'
+ ],
+ [
+ 1495708654.925,
+ '7.9296875'
+ ],
+ [
+ 1495708714.925,
+ '7.9296875'
+ ],
+ [
+ 1495708774.925,
+ '7.9296875'
+ ],
+ [
+ 1495708834.925,
+ '7.9296875'
+ ],
+ [
+ 1495708894.925,
+ '7.9296875'
+ ],
+ [
+ 1495708954.925,
+ '7.8984375'
+ ],
+ [
+ 1495709014.925,
+ '7.8984375'
+ ],
+ [
+ 1495709074.925,
+ '7.8984375'
+ ],
+ [
+ 1495709134.925,
+ '7.8984375'
+ ],
+ [
+ 1495709194.925,
+ '7.8984375'
+ ],
+ [
+ 1495709254.925,
+ '7.89453125'
+ ],
+ [
+ 1495709314.925,
+ '7.89453125'
+ ],
+ [
+ 1495709374.925,
+ '7.89453125'
+ ],
+ [
+ 1495709434.925,
+ '7.89453125'
+ ],
+ [
+ 1495709494.925,
+ '7.89453125'
+ ],
+ [
+ 1495709554.925,
+ '7.89453125'
+ ],
+ [
+ 1495709614.925,
+ '7.89453125'
+ ],
+ [
+ 1495709674.925,
+ '7.89453125'
+ ],
+ [
+ 1495709734.925,
+ '7.89453125'
+ ],
+ [
+ 1495709794.925,
+ '7.89453125'
+ ],
+ [
+ 1495709854.925,
+ '7.89453125'
+ ],
+ [
+ 1495709914.925,
+ '7.89453125'
+ ],
+ [
+ 1495709974.925,
+ '7.89453125'
+ ],
+ [
+ 1495710034.925,
+ '7.89453125'
+ ],
+ [
+ 1495710094.925,
+ '7.89453125'
+ ],
+ [
+ 1495710154.925,
+ '7.89453125'
+ ],
+ [
+ 1495710214.925,
+ '7.89453125'
+ ],
+ [
+ 1495710274.925,
+ '7.89453125'
+ ],
+ [
+ 1495710334.925,
+ '7.89453125'
+ ],
+ [
+ 1495710394.925,
+ '7.89453125'
+ ],
+ [
+ 1495710454.925,
+ '7.89453125'
+ ],
+ [
+ 1495710514.925,
+ '7.89453125'
+ ],
+ [
+ 1495710574.925,
+ '7.89453125'
+ ],
+ [
+ 1495710634.925,
+ '7.89453125'
+ ],
+ [
+ 1495710694.925,
+ '7.89453125'
+ ],
+ [
+ 1495710754.925,
+ '7.89453125'
+ ],
+ [
+ 1495710814.925,
+ '7.89453125'
+ ],
+ [
+ 1495710874.925,
+ '7.89453125'
+ ],
+ [
+ 1495710934.925,
+ '7.89453125'
+ ],
+ [
+ 1495710994.925,
+ '7.89453125'
+ ],
+ [
+ 1495711054.925,
+ '7.89453125'
+ ],
+ [
+ 1495711114.925,
+ '7.89453125'
+ ],
+ [
+ 1495711174.925,
+ '7.8515625'
+ ],
+ [
+ 1495711234.925,
+ '7.8515625'
+ ],
+ [
+ 1495711294.925,
+ '7.8515625'
+ ],
+ [
+ 1495711354.925,
+ '7.8515625'
+ ],
+ [
+ 1495711414.925,
+ '7.8515625'
+ ],
+ [
+ 1495711474.925,
+ '7.8515625'
+ ],
+ [
+ 1495711534.925,
+ '7.8515625'
+ ],
+ [
+ 1495711594.925,
+ '7.8515625'
+ ],
+ [
+ 1495711654.925,
+ '7.8515625'
+ ],
+ [
+ 1495711714.925,
+ '7.8515625'
+ ],
+ [
+ 1495711774.925,
+ '7.8515625'
+ ],
+ [
+ 1495711834.925,
+ '7.8515625'
+ ],
+ [
+ 1495711894.925,
+ '7.8515625'
+ ],
+ [
+ 1495711954.925,
+ '7.8515625'
+ ],
+ [
+ 1495712014.925,
+ '7.8515625'
+ ],
+ [
+ 1495712074.925,
+ '7.8515625'
+ ],
+ [
+ 1495712134.925,
+ '7.8515625'
+ ],
+ [
+ 1495712194.925,
+ '7.8515625'
+ ],
+ [
+ 1495712254.925,
+ '7.8515625'
+ ],
+ [
+ 1495712314.925,
+ '7.8515625'
+ ],
+ [
+ 1495712374.925,
+ '7.8515625'
+ ],
+ [
+ 1495712434.925,
+ '7.83203125'
+ ],
+ [
+ 1495712494.925,
+ '7.83203125'
+ ],
+ [
+ 1495712554.925,
+ '7.83203125'
+ ],
+ [
+ 1495712614.925,
+ '7.83203125'
+ ],
+ [
+ 1495712674.925,
+ '7.83203125'
+ ],
+ [
+ 1495712734.925,
+ '7.83203125'
+ ],
+ [
+ 1495712794.925,
+ '7.83203125'
+ ],
+ [
+ 1495712854.925,
+ '7.83203125'
+ ],
+ [
+ 1495712914.925,
+ '7.83203125'
+ ],
+ [
+ 1495712974.925,
+ '7.83203125'
+ ],
+ [
+ 1495713034.925,
+ '7.83203125'
+ ],
+ [
+ 1495713094.925,
+ '7.83203125'
+ ],
+ [
+ 1495713154.925,
+ '7.83203125'
+ ],
+ [
+ 1495713214.925,
+ '7.83203125'
+ ],
+ [
+ 1495713274.925,
+ '7.83203125'
+ ],
+ [
+ 1495713334.925,
+ '7.83203125'
+ ],
+ [
+ 1495713394.925,
+ '7.8125'
+ ],
+ [
+ 1495713454.925,
+ '7.8125'
+ ],
+ [
+ 1495713514.925,
+ '7.8125'
+ ],
+ [
+ 1495713574.925,
+ '7.8125'
+ ],
+ [
+ 1495713634.925,
+ '7.8125'
+ ],
+ [
+ 1495713694.925,
+ '7.8125'
+ ],
+ [
+ 1495713754.925,
+ '7.8125'
+ ],
+ [
+ 1495713814.925,
+ '7.8125'
+ ],
+ [
+ 1495713874.925,
+ '7.8125'
+ ],
+ [
+ 1495713934.925,
+ '7.8125'
+ ],
+ [
+ 1495713994.925,
+ '7.8125'
+ ],
+ [
+ 1495714054.925,
+ '7.8125'
+ ],
+ [
+ 1495714114.925,
+ '7.8125'
+ ],
+ [
+ 1495714174.925,
+ '7.8125'
+ ],
+ [
+ 1495714234.925,
+ '7.8125'
+ ],
+ [
+ 1495714294.925,
+ '7.8125'
+ ],
+ [
+ 1495714354.925,
+ '7.80859375'
+ ],
+ [
+ 1495714414.925,
+ '7.80859375'
+ ],
+ [
+ 1495714474.925,
+ '7.80859375'
+ ],
+ [
+ 1495714534.925,
+ '7.80859375'
+ ],
+ [
+ 1495714594.925,
+ '7.80859375'
+ ],
+ [
+ 1495714654.925,
+ '7.80859375'
+ ],
+ [
+ 1495714714.925,
+ '7.80859375'
+ ],
+ [
+ 1495714774.925,
+ '7.80859375'
+ ],
+ [
+ 1495714834.925,
+ '7.80859375'
+ ],
+ [
+ 1495714894.925,
+ '7.80859375'
+ ],
+ [
+ 1495714954.925,
+ '7.80859375'
+ ],
+ [
+ 1495715014.925,
+ '7.80859375'
+ ],
+ [
+ 1495715074.925,
+ '7.80859375'
+ ],
+ [
+ 1495715134.925,
+ '7.80859375'
+ ],
+ [
+ 1495715194.925,
+ '7.80859375'
+ ],
+ [
+ 1495715254.925,
+ '7.80859375'
+ ],
+ [
+ 1495715314.925,
+ '7.80859375'
+ ],
+ [
+ 1495715374.925,
+ '7.80859375'
+ ],
+ [
+ 1495715434.925,
+ '7.80859375'
+ ],
+ [
+ 1495715494.925,
+ '7.80859375'
+ ],
+ [
+ 1495715554.925,
+ '7.80859375'
+ ],
+ [
+ 1495715614.925,
+ '7.80859375'
+ ],
+ [
+ 1495715674.925,
+ '7.80859375'
+ ],
+ [
+ 1495715734.925,
+ '7.80859375'
+ ],
+ [
+ 1495715794.925,
+ '7.80859375'
+ ],
+ [
+ 1495715854.925,
+ '7.80859375'
+ ],
+ [
+ 1495715914.925,
+ '7.80078125'
+ ],
+ [
+ 1495715974.925,
+ '7.80078125'
+ ],
+ [
+ 1495716034.925,
+ '7.80078125'
+ ],
+ [
+ 1495716094.925,
+ '7.80078125'
+ ],
+ [
+ 1495716154.925,
+ '7.80078125'
+ ],
+ [
+ 1495716214.925,
+ '7.796875'
+ ],
+ [
+ 1495716274.925,
+ '7.796875'
+ ],
+ [
+ 1495716334.925,
+ '7.796875'
+ ],
+ [
+ 1495716394.925,
+ '7.796875'
+ ],
+ [
+ 1495716454.925,
+ '7.796875'
+ ],
+ [
+ 1495716514.925,
+ '7.796875'
+ ],
+ [
+ 1495716574.925,
+ '7.796875'
+ ],
+ [
+ 1495716634.925,
+ '7.796875'
+ ],
+ [
+ 1495716694.925,
+ '7.796875'
+ ],
+ [
+ 1495716754.925,
+ '7.796875'
+ ],
+ [
+ 1495716814.925,
+ '7.796875'
+ ],
+ [
+ 1495716874.925,
+ '7.79296875'
+ ],
+ [
+ 1495716934.925,
+ '7.79296875'
+ ],
+ [
+ 1495716994.925,
+ '7.79296875'
+ ],
+ [
+ 1495717054.925,
+ '7.79296875'
+ ],
+ [
+ 1495717114.925,
+ '7.79296875'
+ ],
+ [
+ 1495717174.925,
+ '7.7890625'
+ ],
+ [
+ 1495717234.925,
+ '7.7890625'
+ ],
+ [
+ 1495717294.925,
+ '7.7890625'
+ ],
+ [
+ 1495717354.925,
+ '7.7890625'
+ ],
+ [
+ 1495717414.925,
+ '7.7890625'
+ ],
+ [
+ 1495717474.925,
+ '7.7890625'
+ ],
+ [
+ 1495717534.925,
+ '7.7890625'
+ ],
+ [
+ 1495717594.925,
+ '7.7890625'
+ ],
+ [
+ 1495717654.925,
+ '7.7890625'
+ ],
+ [
+ 1495717714.925,
+ '7.7890625'
+ ],
+ [
+ 1495717774.925,
+ '7.7890625'
+ ],
+ [
+ 1495717834.925,
+ '7.77734375'
+ ],
+ [
+ 1495717894.925,
+ '7.77734375'
+ ],
+ [
+ 1495717954.925,
+ '7.77734375'
+ ],
+ [
+ 1495718014.925,
+ '7.77734375'
+ ],
+ [
+ 1495718074.925,
+ '7.77734375'
+ ],
+ [
+ 1495718134.925,
+ '7.7421875'
+ ],
+ [
+ 1495718194.925,
+ '7.7421875'
+ ],
+ [
+ 1495718254.925,
+ '7.7421875'
+ ],
+ [
+ 1495718314.925,
+ '7.7421875'
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'title': 'CPU usage',
+ 'weight': 1,
+ 'queries': [
+ {
+ 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+ 'result': [
+ {
+ 'metric': {},
+ 'values': [
+ [
+ 1495700554.925,
+ '0.0010794445585559514'
+ ],
+ [
+ 1495700614.925,
+ '0.003927214935433527'
+ ],
+ [
+ 1495700674.925,
+ '0.0053045219047619975'
+ ],
+ [
+ 1495700734.925,
+ '0.0048892095238097155'
+ ],
+ [
+ 1495700794.925,
+ '0.005827140952381137'
+ ],
+ [
+ 1495700854.925,
+ '0.00569846906219937'
+ ],
+ [
+ 1495700914.925,
+ '0.004972616802849382'
+ ],
+ [
+ 1495700974.925,
+ '0.005117509523809902'
+ ],
+ [
+ 1495701034.925,
+ '0.00512389061919564'
+ ],
+ [
+ 1495701094.925,
+ '0.005199100501890691'
+ ],
+ [
+ 1495701154.925,
+ '0.005415746394885837'
+ ],
+ [
+ 1495701214.925,
+ '0.005607682788146286'
+ ],
+ [
+ 1495701274.925,
+ '0.005641300000000118'
+ ],
+ [
+ 1495701334.925,
+ '0.0071166279368766495'
+ ],
+ [
+ 1495701394.925,
+ '0.0063242138095234044'
+ ],
+ [
+ 1495701454.925,
+ '0.005793314698235304'
+ ],
+ [
+ 1495701514.925,
+ '0.00703934942237556'
+ ],
+ [
+ 1495701574.925,
+ '0.006357007076123191'
+ ],
+ [
+ 1495701634.925,
+ '0.003753167300126738'
+ ],
+ [
+ 1495701694.925,
+ '0.005018469678430698'
+ ],
+ [
+ 1495701754.925,
+ '0.0045217153371887'
+ ],
+ [
+ 1495701814.925,
+ '0.006140104285714119'
+ ],
+ [
+ 1495701874.925,
+ '0.004818684285714102'
+ ],
+ [
+ 1495701934.925,
+ '0.005079509718955242'
+ ],
+ [
+ 1495701994.925,
+ '0.005059981142498263'
+ ],
+ [
+ 1495702054.925,
+ '0.005269098389538773'
+ ],
+ [
+ 1495702114.925,
+ '0.005269954285714175'
+ ],
+ [
+ 1495702174.925,
+ '0.014199241435795856'
+ ],
+ [
+ 1495702234.925,
+ '0.01511936843111017'
+ ],
+ [
+ 1495702294.925,
+ '0.0060933692920682875'
+ ],
+ [
+ 1495702354.925,
+ '0.004945682380952493'
+ ],
+ [
+ 1495702414.925,
+ '0.005641266666666565'
+ ],
+ [
+ 1495702474.925,
+ '0.005223752857142996'
+ ],
+ [
+ 1495702534.925,
+ '0.005743098505699831'
+ ],
+ [
+ 1495702594.925,
+ '0.00538493380952391'
+ ],
+ [
+ 1495702654.925,
+ '0.005507793883751339'
+ ],
+ [
+ 1495702714.925,
+ '0.005666705714285466'
+ ],
+ [
+ 1495702774.925,
+ '0.006231530000000112'
+ ],
+ [
+ 1495702834.925,
+ '0.006570768635394899'
+ ],
+ [
+ 1495702894.925,
+ '0.005551146666666895'
+ ],
+ [
+ 1495702954.925,
+ '0.005602604737098058'
+ ],
+ [
+ 1495703014.925,
+ '0.00613993580402159'
+ ],
+ [
+ 1495703074.925,
+ '0.004770258764368832'
+ ],
+ [
+ 1495703134.925,
+ '0.005512376671364914'
+ ],
+ [
+ 1495703194.925,
+ '0.005254436666666674'
+ ],
+ [
+ 1495703254.925,
+ '0.0050109839141320505'
+ ],
+ [
+ 1495703314.925,
+ '0.0049478019256960016'
+ ],
+ [
+ 1495703374.925,
+ '0.0037666860965123463'
+ ],
+ [
+ 1495703434.925,
+ '0.004813526061656314'
+ ],
+ [
+ 1495703494.925,
+ '0.005047748095238278'
+ ],
+ [
+ 1495703554.925,
+ '0.00386494081008772'
+ ],
+ [
+ 1495703614.925,
+ '0.004304037408111405'
+ ],
+ [
+ 1495703674.925,
+ '0.004999466661587168'
+ ],
+ [
+ 1495703734.925,
+ '0.004689140476190834'
+ ],
+ [
+ 1495703794.925,
+ '0.004746126153582475'
+ ],
+ [
+ 1495703854.925,
+ '0.004482706382572302'
+ ],
+ [
+ 1495703914.925,
+ '0.004032808931864524'
+ ],
+ [
+ 1495703974.925,
+ '0.005728319047618988'
+ ],
+ [
+ 1495704034.925,
+ '0.004436139179627006'
+ ],
+ [
+ 1495704094.925,
+ '0.004553455714285617'
+ ],
+ [
+ 1495704154.925,
+ '0.003455244285714341'
+ ],
+ [
+ 1495704214.925,
+ '0.004742244761904621'
+ ],
+ [
+ 1495704274.925,
+ '0.005366978571428422'
+ ],
+ [
+ 1495704334.925,
+ '0.004257954837665058'
+ ],
+ [
+ 1495704394.925,
+ '0.005431603259831257'
+ ],
+ [
+ 1495704454.925,
+ '0.0052009214498621986'
+ ],
+ [
+ 1495704514.925,
+ '0.004317201904761618'
+ ],
+ [
+ 1495704574.925,
+ '0.004307384285714157'
+ ],
+ [
+ 1495704634.925,
+ '0.004789801146644822'
+ ],
+ [
+ 1495704694.925,
+ '0.0051429795906706485'
+ ],
+ [
+ 1495704754.925,
+ '0.005322495714285479'
+ ],
+ [
+ 1495704814.925,
+ '0.004512809333244233'
+ ],
+ [
+ 1495704874.925,
+ '0.004953843582568726'
+ ],
+ [
+ 1495704934.925,
+ '0.005812690120858119'
+ ],
+ [
+ 1495704994.925,
+ '0.004997024285714838'
+ ],
+ [
+ 1495705054.925,
+ '0.005246216154439592'
+ ],
+ [
+ 1495705114.925,
+ '0.0063494966618726795'
+ ],
+ [
+ 1495705174.925,
+ '0.005306004342898225'
+ ],
+ [
+ 1495705234.925,
+ '0.005081412857142978'
+ ],
+ [
+ 1495705294.925,
+ '0.00511409523809522'
+ ],
+ [
+ 1495705354.925,
+ '0.0047861001481192'
+ ],
+ [
+ 1495705414.925,
+ '0.005107688228042962'
+ ],
+ [
+ 1495705474.925,
+ '0.005271929582294012'
+ ],
+ [
+ 1495705534.925,
+ '0.004453254502681249'
+ ],
+ [
+ 1495705594.925,
+ '0.005799134293959226'
+ ],
+ [
+ 1495705654.925,
+ '0.005340865929502478'
+ ],
+ [
+ 1495705714.925,
+ '0.004911654761904942'
+ ],
+ [
+ 1495705774.925,
+ '0.005888234873953261'
+ ],
+ [
+ 1495705834.925,
+ '0.005565283333332954'
+ ],
+ [
+ 1495705894.925,
+ '0.005522869047618869'
+ ],
+ [
+ 1495705954.925,
+ '0.005177549737621646'
+ ],
+ [
+ 1495706014.925,
+ '0.0053145810232096465'
+ ],
+ [
+ 1495706074.925,
+ '0.004751095238095275'
+ ],
+ [
+ 1495706134.925,
+ '0.006242077142856976'
+ ],
+ [
+ 1495706194.925,
+ '0.00621034406957871'
+ ],
+ [
+ 1495706254.925,
+ '0.006887592738978596'
+ ],
+ [
+ 1495706314.925,
+ '0.006328128779726213'
+ ],
+ [
+ 1495706374.925,
+ '0.007488363809523927'
+ ],
+ [
+ 1495706434.925,
+ '0.006193758571428157'
+ ],
+ [
+ 1495706494.925,
+ '0.0068798371839706935'
+ ],
+ [
+ 1495706554.925,
+ '0.005757034340423128'
+ ],
+ [
+ 1495706614.925,
+ '0.004571388497294698'
+ ],
+ [
+ 1495706674.925,
+ '0.00620283044923395'
+ ],
+ [
+ 1495706734.925,
+ '0.005607562380952455'
+ ],
+ [
+ 1495706794.925,
+ '0.005506969933620308'
+ ],
+ [
+ 1495706854.925,
+ '0.005621118095238131'
+ ],
+ [
+ 1495706914.925,
+ '0.004876606098698849'
+ ],
+ [
+ 1495706974.925,
+ '0.0047871205988517206'
+ ],
+ [
+ 1495707034.925,
+ '0.00526405939458784'
+ ],
+ [
+ 1495707094.925,
+ '0.005716323800605852'
+ ],
+ [
+ 1495707154.925,
+ '0.005301459523809575'
+ ],
+ [
+ 1495707214.925,
+ '0.0051613042857144905'
+ ],
+ [
+ 1495707274.925,
+ '0.005384792857142714'
+ ],
+ [
+ 1495707334.925,
+ '0.005259719047619222'
+ ],
+ [
+ 1495707394.925,
+ '0.00584101142857182'
+ ],
+ [
+ 1495707454.925,
+ '0.0060066121920326326'
+ ],
+ [
+ 1495707514.925,
+ '0.006359978571428453'
+ ],
+ [
+ 1495707574.925,
+ '0.006315876322151109'
+ ],
+ [
+ 1495707634.925,
+ '0.005590012517198831'
+ ],
+ [
+ 1495707694.925,
+ '0.005517419877137072'
+ ],
+ [
+ 1495707754.925,
+ '0.006089813430348506'
+ ],
+ [
+ 1495707814.925,
+ '0.00466754476190479'
+ ],
+ [
+ 1495707874.925,
+ '0.006059954380517721'
+ ],
+ [
+ 1495707934.925,
+ '0.005085657142856972'
+ ],
+ [
+ 1495707994.925,
+ '0.005897665238095296'
+ ],
+ [
+ 1495708054.925,
+ '0.0062282023199555885'
+ ],
+ [
+ 1495708114.925,
+ '0.00526214553236979'
+ ],
+ [
+ 1495708174.925,
+ '0.0044803300000000644'
+ ],
+ [
+ 1495708234.925,
+ '0.005421443333333592'
+ ],
+ [
+ 1495708294.925,
+ '0.005694326244512144'
+ ],
+ [
+ 1495708354.925,
+ '0.005527721904761457'
+ ],
+ [
+ 1495708414.925,
+ '0.005988819523809819'
+ ],
+ [
+ 1495708474.925,
+ '0.005484704285714448'
+ ],
+ [
+ 1495708534.925,
+ '0.005041123649230085'
+ ],
+ [
+ 1495708594.925,
+ '0.005717767639612059'
+ ],
+ [
+ 1495708654.925,
+ '0.005412954417342863'
+ ],
+ [
+ 1495708714.925,
+ '0.005833343333333254'
+ ],
+ [
+ 1495708774.925,
+ '0.005448135238094969'
+ ],
+ [
+ 1495708834.925,
+ '0.005117341428571432'
+ ],
+ [
+ 1495708894.925,
+ '0.005888345825277833'
+ ],
+ [
+ 1495708954.925,
+ '0.005398543809524135'
+ ],
+ [
+ 1495709014.925,
+ '0.005325611428571416'
+ ],
+ [
+ 1495709074.925,
+ '0.005848668571428527'
+ ],
+ [
+ 1495709134.925,
+ '0.005135003105145044'
+ ],
+ [
+ 1495709194.925,
+ '0.0054551400000003'
+ ],
+ [
+ 1495709254.925,
+ '0.005319472937322171'
+ ],
+ [
+ 1495709314.925,
+ '0.00585677857142792'
+ ],
+ [
+ 1495709374.925,
+ '0.0062146261904759215'
+ ],
+ [
+ 1495709434.925,
+ '0.0067105060904182265'
+ ],
+ [
+ 1495709494.925,
+ '0.005829691904762108'
+ ],
+ [
+ 1495709554.925,
+ '0.005719280952381261'
+ ],
+ [
+ 1495709614.925,
+ '0.005682603793416407'
+ ],
+ [
+ 1495709674.925,
+ '0.0055272846277326934'
+ ],
+ [
+ 1495709734.925,
+ '0.0057123680952386735'
+ ],
+ [
+ 1495709794.925,
+ '0.00520597958075818'
+ ],
+ [
+ 1495709854.925,
+ '0.005584358957263837'
+ ],
+ [
+ 1495709914.925,
+ '0.005601104275197466'
+ ],
+ [
+ 1495709974.925,
+ '0.005991657142857066'
+ ],
+ [
+ 1495710034.925,
+ '0.00553722238095218'
+ ],
+ [
+ 1495710094.925,
+ '0.005127883122696293'
+ ],
+ [
+ 1495710154.925,
+ '0.005498111927534584'
+ ],
+ [
+ 1495710214.925,
+ '0.005609934069084202'
+ ],
+ [
+ 1495710274.925,
+ '0.00459206285714307'
+ ],
+ [
+ 1495710334.925,
+ '0.0047910828571428084'
+ ],
+ [
+ 1495710394.925,
+ '0.0056014671288845685'
+ ],
+ [
+ 1495710454.925,
+ '0.005686936791078528'
+ ],
+ [
+ 1495710514.925,
+ '0.00444480476190448'
+ ],
+ [
+ 1495710574.925,
+ '0.005780394696738921'
+ ],
+ [
+ 1495710634.925,
+ '0.0053107227550210365'
+ ],
+ [
+ 1495710694.925,
+ '0.005096031495761817'
+ ],
+ [
+ 1495710754.925,
+ '0.005451377979091524'
+ ],
+ [
+ 1495710814.925,
+ '0.005328136666667083'
+ ],
+ [
+ 1495710874.925,
+ '0.006020612857143043'
+ ],
+ [
+ 1495710934.925,
+ '0.0061063585714285365'
+ ],
+ [
+ 1495710994.925,
+ '0.006018346015752312'
+ ],
+ [
+ 1495711054.925,
+ '0.005069130952381193'
+ ],
+ [
+ 1495711114.925,
+ '0.005458406190476052'
+ ],
+ [
+ 1495711174.925,
+ '0.00577219190476179'
+ ],
+ [
+ 1495711234.925,
+ '0.005760814645658314'
+ ],
+ [
+ 1495711294.925,
+ '0.005371875716579101'
+ ],
+ [
+ 1495711354.925,
+ '0.0064232666666665834'
+ ],
+ [
+ 1495711414.925,
+ '0.009369806836906667'
+ ],
+ [
+ 1495711474.925,
+ '0.008956864761904692'
+ ],
+ [
+ 1495711534.925,
+ '0.005266849368559271'
+ ],
+ [
+ 1495711594.925,
+ '0.005335111364934262'
+ ],
+ [
+ 1495711654.925,
+ '0.006461778319586945'
+ ],
+ [
+ 1495711714.925,
+ '0.004687939890762393'
+ ],
+ [
+ 1495711774.925,
+ '0.004438831245760684'
+ ],
+ [
+ 1495711834.925,
+ '0.005142786666666613'
+ ],
+ [
+ 1495711894.925,
+ '0.007257734212054963'
+ ],
+ [
+ 1495711954.925,
+ '0.005621991904761494'
+ ],
+ [
+ 1495712014.925,
+ '0.007868689999999862'
+ ],
+ [
+ 1495712074.925,
+ '0.00910970215275738'
+ ],
+ [
+ 1495712134.925,
+ '0.006151004285714278'
+ ],
+ [
+ 1495712194.925,
+ '0.005447120924961522'
+ ],
+ [
+ 1495712254.925,
+ '0.005150705153929503'
+ ],
+ [
+ 1495712314.925,
+ '0.006358108714969314'
+ ],
+ [
+ 1495712374.925,
+ '0.0057725354795696475'
+ ],
+ [
+ 1495712434.925,
+ '0.005232139047619015'
+ ],
+ [
+ 1495712494.925,
+ '0.004932809617949037'
+ ],
+ [
+ 1495712554.925,
+ '0.004511607508499662'
+ ],
+ [
+ 1495712614.925,
+ '0.00440487701522666'
+ ],
+ [
+ 1495712674.925,
+ '0.005479113333333174'
+ ],
+ [
+ 1495712734.925,
+ '0.004726317619047547'
+ ],
+ [
+ 1495712794.925,
+ '0.005582041102958029'
+ ],
+ [
+ 1495712854.925,
+ '0.006381481216082099'
+ ],
+ [
+ 1495712914.925,
+ '0.005474260014095208'
+ ],
+ [
+ 1495712974.925,
+ '0.00567597142857188'
+ ],
+ [
+ 1495713034.925,
+ '0.0064741233333332985'
+ ],
+ [
+ 1495713094.925,
+ '0.005467475714285271'
+ ],
+ [
+ 1495713154.925,
+ '0.004868648393824457'
+ ],
+ [
+ 1495713214.925,
+ '0.005254923286444893'
+ ],
+ [
+ 1495713274.925,
+ '0.005599217150312865'
+ ],
+ [
+ 1495713334.925,
+ '0.005105413720618919'
+ ],
+ [
+ 1495713394.925,
+ '0.007246073333333279'
+ ],
+ [
+ 1495713454.925,
+ '0.005990312380952272'
+ ],
+ [
+ 1495713514.925,
+ '0.005594601853351101'
+ ],
+ [
+ 1495713574.925,
+ '0.004739258673727054'
+ ],
+ [
+ 1495713634.925,
+ '0.003932121428571783'
+ ],
+ [
+ 1495713694.925,
+ '0.005018188268459395'
+ ],
+ [
+ 1495713754.925,
+ '0.004538238095237985'
+ ],
+ [
+ 1495713814.925,
+ '0.00561816643265435'
+ ],
+ [
+ 1495713874.925,
+ '0.0063132584495033586'
+ ],
+ [
+ 1495713934.925,
+ '0.00442385238095213'
+ ],
+ [
+ 1495713994.925,
+ '0.004181795887658453'
+ ],
+ [
+ 1495714054.925,
+ '0.004437759047619037'
+ ],
+ [
+ 1495714114.925,
+ '0.006421748157178241'
+ ],
+ [
+ 1495714174.925,
+ '0.006525143809523842'
+ ],
+ [
+ 1495714234.925,
+ '0.004715904935144247'
+ ],
+ [
+ 1495714294.925,
+ '0.005966040152763461'
+ ],
+ [
+ 1495714354.925,
+ '0.005614535466921674'
+ ],
+ [
+ 1495714414.925,
+ '0.004934375119415906'
+ ],
+ [
+ 1495714474.925,
+ '0.0054122933333327385'
+ ],
+ [
+ 1495714534.925,
+ '0.004926540699612279'
+ ],
+ [
+ 1495714594.925,
+ '0.006124649517134237'
+ ],
+ [
+ 1495714654.925,
+ '0.004629427092013995'
+ ],
+ [
+ 1495714714.925,
+ '0.005117951257607005'
+ ],
+ [
+ 1495714774.925,
+ '0.004868774512685422'
+ ],
+ [
+ 1495714834.925,
+ '0.005310093333333399'
+ ],
+ [
+ 1495714894.925,
+ '0.0054907752286127345'
+ ],
+ [
+ 1495714954.925,
+ '0.004597678117351089'
+ ],
+ [
+ 1495715014.925,
+ '0.0059622552380952'
+ ],
+ [
+ 1495715074.925,
+ '0.005352457072655368'
+ ],
+ [
+ 1495715134.925,
+ '0.005491630952381143'
+ ],
+ [
+ 1495715194.925,
+ '0.006391770078379791'
+ ],
+ [
+ 1495715254.925,
+ '0.005933472857142518'
+ ],
+ [
+ 1495715314.925,
+ '0.005301314285714163'
+ ],
+ [
+ 1495715374.925,
+ '0.0058352959724814165'
+ ],
+ [
+ 1495715434.925,
+ '0.006154755147867044'
+ ],
+ [
+ 1495715494.925,
+ '0.009391935637482038'
+ ],
+ [
+ 1495715554.925,
+ '0.007846462857142592'
+ ],
+ [
+ 1495715614.925,
+ '0.00477608215316353'
+ ],
+ [
+ 1495715674.925,
+ '0.006132865238094998'
+ ],
+ [
+ 1495715734.925,
+ '0.006159762457649516'
+ ],
+ [
+ 1495715794.925,
+ '0.005957307073265968'
+ ],
+ [
+ 1495715854.925,
+ '0.006652319091792501'
+ ],
+ [
+ 1495715914.925,
+ '0.005493557402895287'
+ ],
+ [
+ 1495715974.925,
+ '0.0058652434829145166'
+ ],
+ [
+ 1495716034.925,
+ '0.005627400430468021'
+ ],
+ [
+ 1495716094.925,
+ '0.006240656190475609'
+ ],
+ [
+ 1495716154.925,
+ '0.006305997676168624'
+ ],
+ [
+ 1495716214.925,
+ '0.005388057732783248'
+ ],
+ [
+ 1495716274.925,
+ '0.0052814916048421244'
+ ],
+ [
+ 1495716334.925,
+ '0.00699498614272497'
+ ],
+ [
+ 1495716394.925,
+ '0.00627768693035141'
+ ],
+ [
+ 1495716454.925,
+ '0.0042411487048161145'
+ ],
+ [
+ 1495716514.925,
+ '0.005348647473627653'
+ ],
+ [
+ 1495716574.925,
+ '0.0047176657142853975'
+ ],
+ [
+ 1495716634.925,
+ '0.004437898571428686'
+ ],
+ [
+ 1495716694.925,
+ '0.004923527366927261'
+ ],
+ [
+ 1495716754.925,
+ '0.005131935066048421'
+ ],
+ [
+ 1495716814.925,
+ '0.005046949523809611'
+ ],
+ [
+ 1495716874.925,
+ '0.00547184095238092'
+ ],
+ [
+ 1495716934.925,
+ '0.005224140016380444'
+ ],
+ [
+ 1495716994.925,
+ '0.005297991171665292'
+ ],
+ [
+ 1495717054.925,
+ '0.005492965995623498'
+ ],
+ [
+ 1495717114.925,
+ '0.005754660000000403'
+ ],
+ [
+ 1495717174.925,
+ '0.005949557138639285'
+ ],
+ [
+ 1495717234.925,
+ '0.006091816112534666'
+ ],
+ [
+ 1495717294.925,
+ '0.005554210080192063'
+ ],
+ [
+ 1495717354.925,
+ '0.006411504395279871'
+ ],
+ [
+ 1495717414.925,
+ '0.006319643996609606'
+ ],
+ [
+ 1495717474.925,
+ '0.005539174405717675'
+ ],
+ [
+ 1495717534.925,
+ '0.0053157078842772255'
+ ],
+ [
+ 1495717594.925,
+ '0.005247480952381066'
+ ],
+ [
+ 1495717654.925,
+ '0.004820141620396252'
+ ],
+ [
+ 1495717714.925,
+ '0.005906173868322844'
+ ],
+ [
+ 1495717774.925,
+ '0.006173117219570961'
+ ],
+ [
+ 1495717834.925,
+ '0.005963340952380661'
+ ],
+ [
+ 1495717894.925,
+ '0.005698976627681527'
+ ],
+ [
+ 1495717954.925,
+ '0.004751279096346378'
+ ],
+ [
+ 1495718014.925,
+ '0.005733142379359711'
+ ],
+ [
+ 1495718074.925,
+ '0.004831689010348035'
+ ],
+ [
+ 1495718134.925,
+ '0.005188370476191092'
+ ],
+ [
+ 1495718194.925,
+ '0.004793227554547938'
+ ],
+ [
+ 1495718254.925,
+ '0.003997442857142731'
+ ],
+ [
+ 1495718314.925,
+ '0.004386040132951264'
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ 'last_update': '2017-05-25T13:18:34.949Z'
+};
+
+export default metricsGroupsAPIResponse;
+
+const responseMockData = {
+ 'GET': {
+ '/root/hello-prometheus/environments/30/additional_metrics.json': metricsGroupsAPIResponse,
+ 'http://test.host/frontend-fixtures/environments-project/environments/1/additional_metrics.json': metricsGroupsAPIResponse, // TODO: MAke sure this works in the monitoring_bundle_spec
+ },
+};
+
+export const deploymentData = [
+ {
+ id: 111,
+ iid: 3,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ ref: {
+ name: 'master'
+ },
+ created_at: '2017-05-31T21:23:37.881Z',
+ tag: false,
+ 'last?': true
+ },
+ {
+ id: 110,
+ iid: 2,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ ref: {
+ name: 'master'
+ },
+ created_at: '2017-05-30T20:08:04.629Z',
+ tag: false,
+ 'last?': false
+ },
+ {
+ id: 109,
+ iid: 1,
+ sha: '6511e58faafaa7ad2228990ec57f19d66f7db7c2',
+ ref: {
+ name: 'update2-readme'
+ },
+ created_at: '2017-05-30T17:42:38.409Z',
+ tag: false,
+ 'last?': false
+ }
+];
+
+export const statePaths = {
+ settingsPath: '/root/hello-prometheus/services/prometheus/edit',
+ documentationPath: '/help/administration/monitoring/prometheus/index.md',
+};
+
+export const singleRowMetrics = [
+ {
+ 'title': 'CPU usage',
+ 'weight': 1,
+ 'y_label': 'Memory',
+ 'queries': [
+ {
+ 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+ 'label': 'Container CPU',
+ 'result': [
+ {
+ 'metric': {
+
+ },
+ 'values': [
+ {
+ 'time': '2017-06-04T21:22:59.508Z',
+ 'value': '0.06335544298150002'
+ },
+ {
+ 'time': '2017-06-04T21:23:59.508Z',
+ 'value': '0.0420347312480917'
+ },
+ {
+ 'time': '2017-06-04T21:24:59.508Z',
+ 'value': '0.0023175131665412706'
+ },
+ {
+ 'time': '2017-06-04T21:25:59.508Z',
+ 'value': '0.002315870476190476'
+ },
+ {
+ 'time': '2017-06-04T21:26:59.508Z',
+ 'value': '0.0025005961904761894'
+ },
+ {
+ 'time': '2017-06-04T21:27:59.508Z',
+ 'value': '0.0024612605834341264'
+ },
+ {
+ 'time': '2017-06-04T21:28:59.508Z',
+ 'value': '0.002313129398767631'
+ },
+ {
+ 'time': '2017-06-04T21:29:59.508Z',
+ 'value': '0.002411067353663882'
+ },
+ {
+ 'time': '2017-06-04T21:30:59.508Z',
+ 'value': '0.002577309263721303'
+ },
+ {
+ 'time': '2017-06-04T21:31:59.508Z',
+ 'value': '0.00242688307730403'
+ },
+ {
+ 'time': '2017-06-04T21:32:59.508Z',
+ 'value': '0.0024168360301330457'
+ },
+ {
+ 'time': '2017-06-04T21:33:59.508Z',
+ 'value': '0.0020449528090743714'
+ },
+ {
+ 'time': '2017-06-04T21:34:59.508Z',
+ 'value': '0.0019149619047619036'
+ },
+ {
+ 'time': '2017-06-04T21:35:59.508Z',
+ 'value': '0.0024491714364625094'
+ },
+ {
+ 'time': '2017-06-04T21:36:59.508Z',
+ 'value': '0.002728773131172677'
+ },
+ {
+ 'time': '2017-06-04T21:37:59.508Z',
+ 'value': '0.0028439119047618997'
+ },
+ {
+ 'time': '2017-06-04T21:38:59.508Z',
+ 'value': '0.0026307480952380917'
+ },
+ {
+ 'time': '2017-06-04T21:39:59.508Z',
+ 'value': '0.0025024842620546446'
+ },
+ {
+ 'time': '2017-06-04T21:40:59.508Z',
+ 'value': '0.002300662387260825'
+ },
+ {
+ 'time': '2017-06-04T21:41:59.508Z',
+ 'value': '0.002052890924848337'
+ },
+ {
+ 'time': '2017-06-04T21:42:59.508Z',
+ 'value': '0.0023711195238095275'
+ },
+ {
+ 'time': '2017-06-04T21:43:59.508Z',
+ 'value': '0.002513477619047618'
+ },
+ {
+ 'time': '2017-06-04T21:44:59.508Z',
+ 'value': '0.0023489776287844897'
+ },
+ {
+ 'time': '2017-06-04T21:45:59.508Z',
+ 'value': '0.002542572310212481'
+ },
+ {
+ 'time': '2017-06-04T21:46:59.508Z',
+ 'value': '0.0024579470671707952'
+ },
+ {
+ 'time': '2017-06-04T21:47:59.508Z',
+ 'value': '0.0028725150236664403'
+ },
+ {
+ 'time': '2017-06-04T21:48:59.508Z',
+ 'value': '0.0024356089105610525'
+ },
+ {
+ 'time': '2017-06-04T21:49:59.508Z',
+ 'value': '0.002544015828269929'
+ },
+ {
+ 'time': '2017-06-04T21:50:59.508Z',
+ 'value': '0.0029595013380824906'
+ },
+ {
+ 'time': '2017-06-04T21:51:59.508Z',
+ 'value': '0.0023084015085858'
+ },
+ {
+ 'time': '2017-06-04T21:52:59.508Z',
+ 'value': '0.0021070500000000083'
+ },
+ {
+ 'time': '2017-06-04T21:53:59.508Z',
+ 'value': '0.0022950066191106617'
+ },
+ {
+ 'time': '2017-06-04T21:54:59.508Z',
+ 'value': '0.002492719454470995'
+ },
+ {
+ 'time': '2017-06-04T21:55:59.508Z',
+ 'value': '0.00244312761904762'
+ },
+ {
+ 'time': '2017-06-04T21:56:59.508Z',
+ 'value': '0.0023495500000000028'
+ },
+ {
+ 'time': '2017-06-04T21:57:59.508Z',
+ 'value': '0.0020597072353070005'
+ },
+ {
+ 'time': '2017-06-04T21:58:59.508Z',
+ 'value': '0.0021482352044800866'
+ },
+ {
+ 'time': '2017-06-04T21:59:59.508Z',
+ 'value': '0.002333490000000004'
+ },
+ {
+ 'time': '2017-06-04T22:00:59.508Z',
+ 'value': '0.0025899442857142815'
+ },
+ {
+ 'time': '2017-06-04T22:01:59.508Z',
+ 'value': '0.002430299999999999'
+ },
+ {
+ 'time': '2017-06-04T22:02:59.508Z',
+ 'value': '0.0023550328092113476'
+ },
+ {
+ 'time': '2017-06-04T22:03:59.508Z',
+ 'value': '0.0026521871636872793'
+ },
+ {
+ 'time': '2017-06-04T22:04:59.508Z',
+ 'value': '0.0023080671428571398'
+ },
+ {
+ 'time': '2017-06-04T22:05:59.508Z',
+ 'value': '0.0024108401032390896'
+ },
+ {
+ 'time': '2017-06-04T22:06:59.508Z',
+ 'value': '0.002433249366678738'
+ },
+ {
+ 'time': '2017-06-04T22:07:59.508Z',
+ 'value': '0.0023242202306688682'
+ },
+ {
+ 'time': '2017-06-04T22:08:59.508Z',
+ 'value': '0.002388222857142859'
+ },
+ {
+ 'time': '2017-06-04T22:09:59.508Z',
+ 'value': '0.002115974914046794'
+ },
+ {
+ 'time': '2017-06-04T22:10:59.508Z',
+ 'value': '0.0025090043331269917'
+ },
+ {
+ 'time': '2017-06-04T22:11:59.508Z',
+ 'value': '0.002445507057277277'
+ },
+ {
+ 'time': '2017-06-04T22:12:59.508Z',
+ 'value': '0.0026348773751130976'
+ },
+ {
+ 'time': '2017-06-04T22:13:59.508Z',
+ 'value': '0.0025616258583088104'
+ },
+ {
+ 'time': '2017-06-04T22:14:59.508Z',
+ 'value': '0.0021544093415751505'
+ },
+ {
+ 'time': '2017-06-04T22:15:59.508Z',
+ 'value': '0.002649394767668881'
+ },
+ {
+ 'time': '2017-06-04T22:16:59.508Z',
+ 'value': '0.0024023332666685705'
+ },
+ {
+ 'time': '2017-06-04T22:17:59.508Z',
+ 'value': '0.0025444105294235306'
+ },
+ {
+ 'time': '2017-06-04T22:18:59.508Z',
+ 'value': '0.0027298872305772806'
+ },
+ {
+ 'time': '2017-06-04T22:19:59.508Z',
+ 'value': '0.0022880104956379287'
+ },
+ {
+ 'time': '2017-06-04T22:20:59.508Z',
+ 'value': '0.002473246666666661'
+ },
+ {
+ 'time': '2017-06-04T22:21:59.508Z',
+ 'value': '0.002259948381935587'
+ },
+ {
+ 'time': '2017-06-04T22:22:59.508Z',
+ 'value': '0.0025778470886268835'
+ },
+ {
+ 'time': '2017-06-04T22:23:59.508Z',
+ 'value': '0.002246127910852894'
+ },
+ {
+ 'time': '2017-06-04T22:24:59.508Z',
+ 'value': '0.0020697466666666758'
+ },
+ {
+ 'time': '2017-06-04T22:25:59.508Z',
+ 'value': '0.00225859722473547'
+ },
+ {
+ 'time': '2017-06-04T22:26:59.508Z',
+ 'value': '0.0026466728254554814'
+ },
+ {
+ 'time': '2017-06-04T22:27:59.508Z',
+ 'value': '0.002151247619047619'
+ },
+ {
+ 'time': '2017-06-04T22:28:59.508Z',
+ 'value': '0.002324161444543914'
+ },
+ {
+ 'time': '2017-06-04T22:29:59.508Z',
+ 'value': '0.002476474313796452'
+ },
+ {
+ 'time': '2017-06-04T22:30:59.508Z',
+ 'value': '0.0023922184232080517'
+ },
+ {
+ 'time': '2017-06-04T22:31:59.508Z',
+ 'value': '0.0025094934237468933'
+ },
+ {
+ 'time': '2017-06-04T22:32:59.508Z',
+ 'value': '0.0025665311098200883'
+ },
+ {
+ 'time': '2017-06-04T22:33:59.508Z',
+ 'value': '0.0024154900681661374'
+ },
+ {
+ 'time': '2017-06-04T22:34:59.508Z',
+ 'value': '0.0023267450166192037'
+ },
+ {
+ 'time': '2017-06-04T22:35:59.508Z',
+ 'value': '0.002156521904761904'
+ },
+ {
+ 'time': '2017-06-04T22:36:59.508Z',
+ 'value': '0.0025474356898637007'
+ },
+ {
+ 'time': '2017-06-04T22:37:59.508Z',
+ 'value': '0.0025989409624670233'
+ },
+ {
+ 'time': '2017-06-04T22:38:59.508Z',
+ 'value': '0.002348336664762987'
+ },
+ {
+ 'time': '2017-06-04T22:39:59.508Z',
+ 'value': '0.002665888246554726'
+ },
+ {
+ 'time': '2017-06-04T22:40:59.508Z',
+ 'value': '0.002652684787474174'
+ },
+ {
+ 'time': '2017-06-04T22:41:59.508Z',
+ 'value': '0.002472620430865355'
+ },
+ {
+ 'time': '2017-06-04T22:42:59.508Z',
+ 'value': '0.0020616469210110247'
+ },
+ {
+ 'time': '2017-06-04T22:43:59.508Z',
+ 'value': '0.0022434546372311934'
+ },
+ {
+ 'time': '2017-06-04T22:44:59.508Z',
+ 'value': '0.0024469386784827982'
+ },
+ {
+ 'time': '2017-06-04T22:45:59.508Z',
+ 'value': '0.0026192823809523787'
+ },
+ {
+ 'time': '2017-06-04T22:46:59.508Z',
+ 'value': '0.003451999542852798'
+ },
+ {
+ 'time': '2017-06-04T22:47:59.508Z',
+ 'value': '0.0031780314285714288'
+ },
+ {
+ 'time': '2017-06-04T22:48:59.508Z',
+ 'value': '0.0024403352380952415'
+ },
+ {
+ 'time': '2017-06-04T22:49:59.508Z',
+ 'value': '0.001998824761904764'
+ },
+ {
+ 'time': '2017-06-04T22:50:59.508Z',
+ 'value': '0.0023792404761904806'
+ },
+ {
+ 'time': '2017-06-04T22:51:59.508Z',
+ 'value': '0.002725906190476185'
+ },
+ {
+ 'time': '2017-06-04T22:52:59.508Z',
+ 'value': '0.0020989528671155624'
+ },
+ {
+ 'time': '2017-06-04T22:53:59.508Z',
+ 'value': '0.00228808226745016'
+ },
+ {
+ 'time': '2017-06-04T22:54:59.508Z',
+ 'value': '0.0019860807413192147'
+ },
+ {
+ 'time': '2017-06-04T22:55:59.508Z',
+ 'value': '0.0022698085714285897'
+ },
+ {
+ 'time': '2017-06-04T22:56:59.508Z',
+ 'value': '0.0022839098467604415'
+ },
+ {
+ 'time': '2017-06-04T22:57:59.508Z',
+ 'value': '0.002531114761904749'
+ },
+ {
+ 'time': '2017-06-04T22:58:59.508Z',
+ 'value': '0.0028941072550999016'
+ },
+ {
+ 'time': '2017-06-04T22:59:59.508Z',
+ 'value': '0.002547169523809506'
+ },
+ {
+ 'time': '2017-06-04T23:00:59.508Z',
+ 'value': '0.0024062999999999958'
+ },
+ {
+ 'time': '2017-06-04T23:01:59.508Z',
+ 'value': '0.0026939518471604386'
+ },
+ {
+ 'time': '2017-06-04T23:02:59.508Z',
+ 'value': '0.002362901428571429'
+ },
+ {
+ 'time': '2017-06-04T23:03:59.508Z',
+ 'value': '0.002663927142857154'
+ },
+ {
+ 'time': '2017-06-04T23:04:59.508Z',
+ 'value': '0.0026173314285714354'
+ },
+ {
+ 'time': '2017-06-04T23:05:59.508Z',
+ 'value': '0.002326527366406044'
+ },
+ {
+ 'time': '2017-06-04T23:06:59.508Z',
+ 'value': '0.002035313809523809'
+ },
+ {
+ 'time': '2017-06-04T23:07:59.508Z',
+ 'value': '0.002421447414786533'
+ },
+ {
+ 'time': '2017-06-04T23:08:59.508Z',
+ 'value': '0.002898313809523804'
+ },
+ {
+ 'time': '2017-06-04T23:09:59.508Z',
+ 'value': '0.002544891856112907'
+ },
+ {
+ 'time': '2017-06-04T23:10:59.508Z',
+ 'value': '0.002290625356938882'
+ },
+ {
+ 'time': '2017-06-04T23:11:59.508Z',
+ 'value': '0.002483028095238096'
+ },
+ {
+ 'time': '2017-06-04T23:12:59.508Z',
+ 'value': '0.0023396832350784237'
+ },
+ {
+ 'time': '2017-06-04T23:13:59.508Z',
+ 'value': '0.002085529248176153'
+ },
+ {
+ 'time': '2017-06-04T23:14:59.508Z',
+ 'value': '0.0022417815068428012'
+ },
+ {
+ 'time': '2017-06-04T23:15:59.508Z',
+ 'value': '0.002660293333333341'
+ },
+ {
+ 'time': '2017-06-04T23:16:59.508Z',
+ 'value': '0.0029845149093818226'
+ },
+ {
+ 'time': '2017-06-04T23:17:59.508Z',
+ 'value': '0.0027716655079475464'
+ },
+ {
+ 'time': '2017-06-04T23:18:59.508Z',
+ 'value': '0.0025217708908741128'
+ },
+ {
+ 'time': '2017-06-04T23:19:59.508Z',
+ 'value': '0.0025811235131094055'
+ },
+ {
+ 'time': '2017-06-04T23:20:59.508Z',
+ 'value': '0.002209904761904762'
+ },
+ {
+ 'time': '2017-06-04T23:21:59.508Z',
+ 'value': '0.0025053322926383344'
+ },
+ {
+ 'time': '2017-06-04T23:22:59.508Z',
+ 'value': '0.002350917636526411'
+ },
+ {
+ 'time': '2017-06-04T23:23:59.508Z',
+ 'value': '0.0018477500000000078'
+ },
+ {
+ 'time': '2017-06-04T23:24:59.508Z',
+ 'value': '0.002427629523809527'
+ },
+ {
+ 'time': '2017-06-04T23:25:59.508Z',
+ 'value': '0.0019305498147601655'
+ },
+ {
+ 'time': '2017-06-04T23:26:59.508Z',
+ 'value': '0.002097250000000006'
+ },
+ {
+ 'time': '2017-06-04T23:27:59.508Z',
+ 'value': '0.002675020952780041'
+ },
+ {
+ 'time': '2017-06-04T23:28:59.508Z',
+ 'value': '0.0023142214285714374'
+ },
+ {
+ 'time': '2017-06-04T23:29:59.508Z',
+ 'value': '0.0023644723809523737'
+ },
+ {
+ 'time': '2017-06-04T23:30:59.508Z',
+ 'value': '0.002108696190476198'
+ },
+ {
+ 'time': '2017-06-04T23:31:59.508Z',
+ 'value': '0.0019918289697997194'
+ },
+ {
+ 'time': '2017-06-04T23:32:59.508Z',
+ 'value': '0.001583584285714283'
+ },
+ {
+ 'time': '2017-06-04T23:33:59.508Z',
+ 'value': '0.002073770226383112'
+ },
+ {
+ 'time': '2017-06-04T23:34:59.508Z',
+ 'value': '0.0025877664234966818'
+ },
+ {
+ 'time': '2017-06-04T23:35:59.508Z',
+ 'value': '0.0021138238095238147'
+ },
+ {
+ 'time': '2017-06-04T23:36:59.508Z',
+ 'value': '0.0022140838095238303'
+ },
+ {
+ 'time': '2017-06-04T23:37:59.508Z',
+ 'value': '0.0018592674425248847'
+ },
+ {
+ 'time': '2017-06-04T23:38:59.508Z',
+ 'value': '0.0020461969533657016'
+ },
+ {
+ 'time': '2017-06-04T23:39:59.508Z',
+ 'value': '0.0021593628571428543'
+ },
+ {
+ 'time': '2017-06-04T23:40:59.508Z',
+ 'value': '0.0024330682564928188'
+ },
+ {
+ 'time': '2017-06-04T23:41:59.508Z',
+ 'value': '0.0021501804779093174'
+ },
+ {
+ 'time': '2017-06-04T23:42:59.508Z',
+ 'value': '0.0025787493928397945'
+ },
+ {
+ 'time': '2017-06-04T23:43:59.508Z',
+ 'value': '0.002593657082448396'
+ },
+ {
+ 'time': '2017-06-04T23:44:59.508Z',
+ 'value': '0.0021316752380952306'
+ },
+ {
+ 'time': '2017-06-04T23:45:59.508Z',
+ 'value': '0.0026972905019952086'
+ },
+ {
+ 'time': '2017-06-04T23:46:59.508Z',
+ 'value': '0.002580250764292983'
+ },
+ {
+ 'time': '2017-06-04T23:47:59.508Z',
+ 'value': '0.00227103000000001'
+ },
+ {
+ 'time': '2017-06-04T23:48:59.508Z',
+ 'value': '0.0023678515647321146'
+ },
+ {
+ 'time': '2017-06-04T23:49:59.508Z',
+ 'value': '0.002371472857142866'
+ },
+ {
+ 'time': '2017-06-04T23:50:59.508Z',
+ 'value': '0.0026181353688500978'
+ },
+ {
+ 'time': '2017-06-04T23:51:59.508Z',
+ 'value': '0.0025609667711121217'
+ },
+ {
+ 'time': '2017-06-04T23:52:59.508Z',
+ 'value': '0.0027145308139922557'
+ },
+ {
+ 'time': '2017-06-04T23:53:59.508Z',
+ 'value': '0.0024249397613310512'
+ },
+ {
+ 'time': '2017-06-04T23:54:59.508Z',
+ 'value': '0.002399907142857147'
+ },
+ {
+ 'time': '2017-06-04T23:55:59.508Z',
+ 'value': '0.0024753357142857195'
+ },
+ {
+ 'time': '2017-06-04T23:56:59.508Z',
+ 'value': '0.0026179149325231575'
+ },
+ {
+ 'time': '2017-06-04T23:57:59.508Z',
+ 'value': '0.0024261340368186956'
+ },
+ {
+ 'time': '2017-06-04T23:58:59.508Z',
+ 'value': '0.0021061071428571517'
+ },
+ {
+ 'time': '2017-06-04T23:59:59.508Z',
+ 'value': '0.0024033971105037015'
+ },
+ {
+ 'time': '2017-06-05T00:00:59.508Z',
+ 'value': '0.0028287676190475956'
+ },
+ {
+ 'time': '2017-06-05T00:01:59.508Z',
+ 'value': '0.002499719050294778'
+ },
+ {
+ 'time': '2017-06-05T00:02:59.508Z',
+ 'value': '0.0026726102153353856'
+ },
+ {
+ 'time': '2017-06-05T00:03:59.508Z',
+ 'value': '0.00262582619047618'
+ },
+ {
+ 'time': '2017-06-05T00:04:59.508Z',
+ 'value': '0.002280473147363316'
+ },
+ {
+ 'time': '2017-06-05T00:05:59.508Z',
+ 'value': '0.002095581470652675'
+ },
+ {
+ 'time': '2017-06-05T00:06:59.508Z',
+ 'value': '0.002270768490828408'
+ },
+ {
+ 'time': '2017-06-05T00:07:59.508Z',
+ 'value': '0.002728577415023017'
+ },
+ {
+ 'time': '2017-06-05T00:08:59.508Z',
+ 'value': '0.002652512857142863'
+ },
+ {
+ 'time': '2017-06-05T00:09:59.508Z',
+ 'value': '0.0022781033924455674'
+ },
+ {
+ 'time': '2017-06-05T00:10:59.508Z',
+ 'value': '0.0025345038095238234'
+ },
+ {
+ 'time': '2017-06-05T00:11:59.508Z',
+ 'value': '0.002376050020000397'
+ },
+ {
+ 'time': '2017-06-05T00:12:59.508Z',
+ 'value': '0.002455068143506122'
+ },
+ {
+ 'time': '2017-06-05T00:13:59.508Z',
+ 'value': '0.002826705714285719'
+ },
+ {
+ 'time': '2017-06-05T00:14:59.508Z',
+ 'value': '0.002343833692070314'
+ },
+ {
+ 'time': '2017-06-05T00:15:59.508Z',
+ 'value': '0.00264853297122164'
+ },
+ {
+ 'time': '2017-06-05T00:16:59.508Z',
+ 'value': '0.0027656335117426257'
+ },
+ {
+ 'time': '2017-06-05T00:17:59.508Z',
+ 'value': '0.0025896543842439564'
+ },
+ {
+ 'time': '2017-06-05T00:18:59.508Z',
+ 'value': '0.002180053237081201'
+ },
+ {
+ 'time': '2017-06-05T00:19:59.508Z',
+ 'value': '0.002475245002333342'
+ },
+ {
+ 'time': '2017-06-05T00:20:59.508Z',
+ 'value': '0.0027559767805101065'
+ },
+ {
+ 'time': '2017-06-05T00:21:59.508Z',
+ 'value': '0.0022294836141296607'
+ },
+ {
+ 'time': '2017-06-05T00:22:59.508Z',
+ 'value': '0.0021383590476190643'
+ },
+ {
+ 'time': '2017-06-05T00:23:59.508Z',
+ 'value': '0.002085417956361494'
+ },
+ {
+ 'time': '2017-06-05T00:24:59.508Z',
+ 'value': '0.0024140319047619013'
+ },
+ {
+ 'time': '2017-06-05T00:25:59.508Z',
+ 'value': '0.0024513114285714304'
+ },
+ {
+ 'time': '2017-06-05T00:26:59.508Z',
+ 'value': '0.0026932152380952446'
+ },
+ {
+ 'time': '2017-06-05T00:27:59.508Z',
+ 'value': '0.0022656844350898517'
+ },
+ {
+ 'time': '2017-06-05T00:28:59.508Z',
+ 'value': '0.0024483785714285704'
+ },
+ {
+ 'time': '2017-06-05T00:29:59.508Z',
+ 'value': '0.002559505804817207'
+ },
+ {
+ 'time': '2017-06-05T00:30:59.508Z',
+ 'value': '0.0019485681088751649'
+ },
+ {
+ 'time': '2017-06-05T00:31:59.508Z',
+ 'value': '0.00228367984456996'
+ },
+ {
+ 'time': '2017-06-05T00:32:59.508Z',
+ 'value': '0.002522149047619049'
+ },
+ {
+ 'time': '2017-06-05T00:33:59.508Z',
+ 'value': '0.0026860117715406737'
+ },
+ {
+ 'time': '2017-06-05T00:34:59.508Z',
+ 'value': '0.002679669523809523'
+ },
+ {
+ 'time': '2017-06-05T00:35:59.508Z',
+ 'value': '0.0022201920970675937'
+ },
+ {
+ 'time': '2017-06-05T00:36:59.508Z',
+ 'value': '0.0022917647619047615'
+ },
+ {
+ 'time': '2017-06-05T00:37:59.508Z',
+ 'value': '0.0021774059294673576'
+ },
+ {
+ 'time': '2017-06-05T00:38:59.508Z',
+ 'value': '0.0024637766666666763'
+ },
+ {
+ 'time': '2017-06-05T00:39:59.508Z',
+ 'value': '0.002470468290174195'
+ },
+ {
+ 'time': '2017-06-05T00:40:59.508Z',
+ 'value': '0.0022188616082057812'
+ },
+ {
+ 'time': '2017-06-05T00:41:59.508Z',
+ 'value': '0.002421840744373875'
+ },
+ {
+ 'time': '2017-06-05T00:42:59.508Z',
+ 'value': '0.0023918266666666547'
+ },
+ {
+ 'time': '2017-06-05T00:43:59.508Z',
+ 'value': '0.002195743809523809'
+ },
+ {
+ 'time': '2017-06-05T00:44:59.508Z',
+ 'value': '0.0025514828571428687'
+ },
+ {
+ 'time': '2017-06-05T00:45:59.508Z',
+ 'value': '0.0027981709349612694'
+ },
+ {
+ 'time': '2017-06-05T00:46:59.508Z',
+ 'value': '0.002557977142857146'
+ },
+ {
+ 'time': '2017-06-05T00:47:59.508Z',
+ 'value': '0.002213244285714286'
+ },
+ {
+ 'time': '2017-06-05T00:48:59.508Z',
+ 'value': '0.0025706738095238046'
+ },
+ {
+ 'time': '2017-06-05T00:49:59.508Z',
+ 'value': '0.002210976666666671'
+ },
+ {
+ 'time': '2017-06-05T00:50:59.508Z',
+ 'value': '0.002055377091646749'
+ },
+ {
+ 'time': '2017-06-05T00:51:59.508Z',
+ 'value': '0.002308368095238119'
+ },
+ {
+ 'time': '2017-06-05T00:52:59.508Z',
+ 'value': '0.0024687939885141615'
+ },
+ {
+ 'time': '2017-06-05T00:53:59.508Z',
+ 'value': '0.002563018571428578'
+ },
+ {
+ 'time': '2017-06-05T00:54:59.508Z',
+ 'value': '0.00240563291078959'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'title': 'Memory usage',
+ 'weight': 1,
+ 'y_label': 'Values',
+ 'queries': [
+ {
+ 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
+ 'label': 'Container memory',
+ 'unit': 'MiB',
+ 'result': [
+ {
+ 'metric': {
+
+ },
+ 'values': [
+ {
+ 'time': '2017-06-04T21:22:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:23:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:24:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:25:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:26:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:27:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:28:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:29:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:30:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:31:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:32:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:33:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:34:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:35:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:36:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:37:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:38:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:39:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:40:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:41:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:42:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:43:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:44:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:45:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:46:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:47:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:48:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:49:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:50:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:51:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:52:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:53:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:54:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:55:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:56:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:57:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:58:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T21:59:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:00:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:01:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:02:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:03:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:04:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:05:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:06:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:07:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:08:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:09:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:10:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:11:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:12:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:13:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:14:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:15:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:16:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:17:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:18:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:19:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:20:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:21:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:22:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:23:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:24:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:25:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:26:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:27:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:28:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:29:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:30:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:31:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:32:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:33:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:34:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:35:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:36:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:37:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:38:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:39:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:40:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:41:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:42:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:43:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:44:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:45:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:46:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:47:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:48:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:49:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:50:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:51:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:52:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:53:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:54:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:55:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:56:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:57:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:58:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T22:59:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:00:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:01:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:02:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:03:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:04:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:05:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:06:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:07:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:08:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:09:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:10:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:11:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:12:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:13:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:14:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:15:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:16:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:17:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:18:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:19:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:20:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:21:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:22:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:23:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:24:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:25:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:26:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:27:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:28:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:29:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:30:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:31:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:32:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:33:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:34:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:35:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:36:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:37:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:38:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:39:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:40:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:41:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:42:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:43:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:44:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:45:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:46:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:47:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:48:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:49:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:50:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:51:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:52:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:53:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:54:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:55:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:56:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:57:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:58:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-04T23:59:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:00:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:01:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:02:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:03:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:04:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:05:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:06:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:07:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:08:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:09:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:10:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:11:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:12:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:13:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:14:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:15:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:16:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:17:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:18:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:19:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:20:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:21:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:22:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:23:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:24:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:25:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:26:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:27:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:28:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:29:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:30:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:31:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:32:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:33:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:34:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:35:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:36:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:37:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:38:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:39:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:40:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:41:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:42:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:43:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:44:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:45:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:46:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:47:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:48:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:49:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:50:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:51:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:52:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:53:59.508Z',
+ 'value': '15.0859375'
+ },
+ {
+ 'time': '2017-06-05T00:54:59.508Z',
+ 'value': '15.0859375'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+];
+
+export function MonitorMockInterceptor(request, next) {
+ const body = responseMockData[request.method.toUpperCase()][request.url];
+
+ next(request.respondWith(JSON.stringify(body), {
+ status: 200,
+ }));
+}
diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js
new file mode 100644
index 00000000000..c423024dce0
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_column_spec.js
@@ -0,0 +1,109 @@
+import Vue from 'vue';
+import _ from 'underscore';
+import MonitoringColumn from '~/monitoring/components/monitoring_column.vue';
+import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
+import eventHub from '~/monitoring/event_hub';
+import { deploymentData, singleRowMetrics } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringColumn);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+describe('MonitoringColumn', () => {
+ beforeEach(() => {
+ spyOn(MonitoringMixins.methods, 'formatDeployments').and.callFake(function fakeFormat() {
+ return {};
+ });
+ });
+
+ it('has a title', () => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.columnData.title);
+ });
+
+ it('creates a path for the line and area of the graph', (done) => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ Vue.nextTick(() => {
+ expect(component.area).toBeDefined();
+ expect(component.line).toBeDefined();
+ expect(typeof component.area).toEqual('string');
+ expect(typeof component.line).toEqual('string');
+ expect(_.isFunction(component.xScale)).toBe(true);
+ expect(_.isFunction(component.yScale)).toBe(true);
+ done();
+ });
+ });
+
+ describe('Computed props', () => {
+ it('axisTransform translates an element Y position depending of its height', () => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ const transformedHeight = `${component.graphHeight - 100}`;
+ expect(component.axisTransform.indexOf(transformedHeight))
+ .not.toEqual(-1);
+ });
+
+ it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ const viewBoxArray = component.outterViewBox.split(' ');
+ expect(typeof component.outterViewBox).toEqual('string');
+ expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
+ expect(viewBoxArray[3]).toEqual(component.graphHeight.toString());
+ });
+ });
+
+ it('sends an event to the eventhub when it has finished resizing', (done) => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+ spyOn(eventHub, '$emit');
+
+ component.updateAspectRatio = true;
+ Vue.nextTick(() => {
+ expect(eventHub.$emit).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('has a title for the y-axis and the chart legend that comes from the backend', () => {
+ const component = createComponent({
+ columnData: singleRowMetrics[0],
+ classType: 'col-md-6',
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.yAxisLabel).toEqual(component.columnData.y_label);
+ expect(component.legendTitle).toEqual(component.columnData.queries[0].label);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_deployment_spec.js b/spec/javascripts/monitoring/monitoring_deployment_spec.js
new file mode 100644
index 00000000000..5cc5b514824
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_deployment_spec.js
@@ -0,0 +1,137 @@
+import Vue from 'vue';
+import MonitoringState from '~/monitoring/components/monitoring_deployment.vue';
+import { deploymentData } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringState);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+describe('MonitoringDeployment', () => {
+ const reducedDeploymentData = [deploymentData[0]];
+ reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name;
+ reducedDeploymentData[0].xPos = 10;
+ reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at);
+ describe('Methods', () => {
+ it('refText shows the ref when a tag is available', () => {
+ reducedDeploymentData[0].tag = '1.0';
+ const component = createComponent({
+ showDeployInfo: false,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.refText(reducedDeploymentData[0]),
+ ).toEqual(reducedDeploymentData[0].ref);
+ });
+
+ it('refText shows the sha when no tag is available', () => {
+ reducedDeploymentData[0].tag = null;
+ const component = createComponent({
+ showDeployInfo: false,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.refText(reducedDeploymentData[0]),
+ ).toContain('f5bcd1');
+ });
+
+ it('nameDeploymentClass creates a class with the prefix deploy-info-', () => {
+ const component = createComponent({
+ showDeployInfo: false,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.nameDeploymentClass(reducedDeploymentData[0]),
+ ).toContain('deploy-info');
+ });
+
+ it('transformDeploymentGroup translates an available deployment', () => {
+ const component = createComponent({
+ showDeployInfo: false,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.transformDeploymentGroup(reducedDeploymentData[0]),
+ ).toContain('translate(11, 20)');
+ });
+
+ it('hides the deployment flag', () => {
+ reducedDeploymentData[0].showDeploymentFlag = false;
+ const component = createComponent({
+ showDeployInfo: true,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull();
+ });
+
+ it('shows the deployment flag', () => {
+ reducedDeploymentData[0].showDeploymentFlag = true;
+ const component = createComponent({
+ showDeployInfo: true,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.$el.querySelector('.js-deploy-info-box').style.display,
+ ).not.toEqual('display: none;');
+ });
+
+ it('shows the refText inside a text element with the deploy-info-text class', () => {
+ reducedDeploymentData[0].showDeploymentFlag = true;
+ const component = createComponent({
+ showDeployInfo: true,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(
+ component.$el.querySelector('.deploy-info-text').firstChild.nodeValue.trim(),
+ ).toEqual(component.refText(reducedDeploymentData[0]));
+ });
+
+ it('should contain a hidden gradient', () => {
+ const component = createComponent({
+ showDeployInfo: true,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
+ });
+
+ describe('Computed props', () => {
+ it('calculatedHeight', () => {
+ const component = createComponent({
+ showDeployInfo: true,
+ deploymentData: reducedDeploymentData,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(component.calculatedHeight).toEqual(180);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_flag_spec.js b/spec/javascripts/monitoring/monitoring_flag_spec.js
new file mode 100644
index 00000000000..3861a95ff07
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_flag_spec.js
@@ -0,0 +1,76 @@
+import Vue from 'vue';
+import MonitoringFlag from '~/monitoring/components/monitoring_flag.vue';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringFlag);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+function getCoordinate(component, selector, coordinate) {
+ const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate);
+ return parseInt(coordinateVal, 10);
+}
+
+describe('MonitoringFlag', () => {
+ it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => {
+ const component = createComponent({
+ currentXCoordinate: 200,
+ currentYCoordinate: 100,
+ currentFlagPosition: 100,
+ currentData: {
+ time: new Date('2017-06-04T18:17:33.501Z'),
+ value: '1.49609375',
+ },
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(getCoordinate(component, '.selected-metric-line', 'x1'))
+ .toEqual(component.currentXCoordinate);
+ expect(getCoordinate(component, '.selected-metric-line', 'x2'))
+ .toEqual(component.currentXCoordinate);
+ expect(getCoordinate(component, '.circle-metric', 'cx'))
+ .toEqual(component.currentXCoordinate);
+ expect(getCoordinate(component, '.circle-metric', 'cy'))
+ .toEqual(component.currentYCoordinate);
+ });
+
+ it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
+ const component = createComponent({
+ currentXCoordinate: 200,
+ currentYCoordinate: 100,
+ currentFlagPosition: 100,
+ currentData: {
+ time: new Date('2017-06-04T18:17:33.501Z'),
+ value: '1.49609375',
+ },
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ const svg = component.$el.querySelector('.rect-text-metric');
+ expect(svg.tagName).toEqual('svg');
+ expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition);
+ });
+
+ describe('Computed props', () => {
+ it('calculatedHeight', () => {
+ const component = createComponent({
+ currentXCoordinate: 200,
+ currentYCoordinate: 100,
+ currentFlagPosition: 100,
+ currentData: {
+ time: new Date('2017-06-04T18:17:33.501Z'),
+ value: '1.49609375',
+ },
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ });
+
+ expect(component.calculatedHeight).toEqual(180);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_legends_spec.js b/spec/javascripts/monitoring/monitoring_legends_spec.js
new file mode 100644
index 00000000000..4c69b81e650
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_legends_spec.js
@@ -0,0 +1,111 @@
+import Vue from 'vue';
+import MonitoringLegends from '~/monitoring/components/monitoring_legends.vue';
+import measurements from '~/monitoring/utils/measurements';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringLegends);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+function getTextFromNode(component, selector) {
+ return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('MonitoringLegends', () => {
+ describe('Computed props', () => {
+ it('textTransform', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
+ });
+
+ it('xPosition', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(component.xPosition).toEqual(180);
+ });
+
+ it('yPosition', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(component.yPosition).toEqual(240);
+ });
+
+ it('rectTransform', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
+ });
+ });
+
+ it('has 2 rect-axis-text rect svg elements', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
+ });
+
+ it('contains text to signal the usage, title and time', () => {
+ const component = createComponent({
+ graphWidth: 500,
+ graphHeight: 300,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ });
+
+ expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle);
+ expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage);
+ expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_row_spec.js b/spec/javascripts/monitoring/monitoring_row_spec.js
new file mode 100644
index 00000000000..a82480e8342
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_row_spec.js
@@ -0,0 +1,57 @@
+import Vue from 'vue';
+import MonitoringRow from '~/monitoring/components/monitoring_row.vue';
+import { deploymentData, singleRowMetrics } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringRow);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+describe('MonitoringRow', () => {
+ describe('Computed props', () => {
+ it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
+ const component = createComponent({
+ rowData: singleRowMetrics,
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.bootstrapClass).toEqual('col-md-6');
+ });
+
+ it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
+ const component = createComponent({
+ rowData: [singleRowMetrics[0]],
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.bootstrapClass).toEqual('col-md-12');
+ });
+ });
+
+ it('has one column', () => {
+ const component = createComponent({
+ rowData: singleRowMetrics,
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.$el.querySelectorAll('.prometheus-svg-container').length)
+ .toEqual(component.rowData.length);
+ });
+
+ it('has two columns', () => {
+ const component = createComponent({
+ rowData: singleRowMetrics,
+ updateAspectRatio: false,
+ deploymentData,
+ });
+
+ expect(component.$el.querySelectorAll('.col-md-6').length)
+ .toEqual(component.rowData.length);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_spec.js b/spec/javascripts/monitoring/monitoring_spec.js
new file mode 100644
index 00000000000..6c7b691baa4
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_spec.js
@@ -0,0 +1,49 @@
+import Vue from 'vue';
+import Monitoring from '~/monitoring/components/monitoring.vue';
+import { MonitorMockInterceptor } from './mock_data';
+
+describe('Monitoring', () => {
+ const fixtureName = 'environments/metrics/metrics.html.raw';
+ let MonitoringComponent;
+ let component;
+ preloadFixtures(fixtureName);
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ MonitoringComponent = Vue.extend(Monitoring);
+ });
+
+ describe('no metrics are available yet', () => {
+ it('shows a getting started empty state when no metrics are present', () => {
+ component = new MonitoringComponent({
+ el: document.querySelector('#prometheus-graphs'),
+ });
+
+ component.$mount();
+ expect(component.$el.querySelector('#prometheus-graphs')).toBe(null);
+ expect(component.state).toEqual('gettingStarted');
+ });
+ });
+
+ describe('requests information to the server', () => {
+ beforeEach(() => {
+ document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
+ Vue.http.interceptors.push(MonitorMockInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor);
+ });
+
+ it('shows up a loading state', (done) => {
+ component = new MonitoringComponent({
+ el: document.querySelector('#prometheus-graphs'),
+ });
+ component.$mount();
+ Vue.nextTick(() => {
+ expect(component.state).toEqual('loading');
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_state_spec.js b/spec/javascripts/monitoring/monitoring_state_spec.js
new file mode 100644
index 00000000000..4c0c558502f
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_state_spec.js
@@ -0,0 +1,110 @@
+import Vue from 'vue';
+import MonitoringState from '~/monitoring/components/monitoring_state.vue';
+import { statePaths } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringState);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+function getTextFromNode(component, selector) {
+ return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('MonitoringState', () => {
+ describe('Computed props', () => {
+ it('currentState', () => {
+ const component = createComponent({
+ selectedState: 'gettingStarted',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.currentState).toBe(component.states.gettingStarted);
+ });
+
+ it('buttonPath returns settings path for the state "gettingStarted"', () => {
+ const component = createComponent({
+ selectedState: 'gettingStarted',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.buttonPath).toEqual(statePaths.settingsPath);
+ expect(component.buttonPath).not.toEqual(statePaths.documentationPath);
+ });
+
+ it('buttonPath returns documentation path for any of the other states', () => {
+ const component = createComponent({
+ selectedState: 'loading',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.buttonPath).toEqual(statePaths.documentationPath);
+ expect(component.buttonPath).not.toEqual(statePaths.settingsPath);
+ });
+
+ it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
+ const component = createComponent({
+ selectedState: 'unableToConnect',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.showButtonDescription).toEqual(true);
+ });
+
+ it('showButtonDescription returns the description without a link for any other state', () => {
+ const component = createComponent({
+ selectedState: 'loading',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.showButtonDescription).toEqual(false);
+ });
+ });
+
+ it('should show the gettingStarted state', () => {
+ const component = createComponent({
+ selectedState: 'gettingStarted',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.$el.querySelector('svg')).toBeDefined();
+ expect(getTextFromNode(component, '.state-title')).toEqual(component.states.gettingStarted.title);
+ expect(getTextFromNode(component, '.state-description')).toEqual(component.states.gettingStarted.description);
+ expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.gettingStarted.buttonText);
+ });
+
+ it('should show the loading state', () => {
+ const component = createComponent({
+ selectedState: 'loading',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.$el.querySelector('svg')).toBeDefined();
+ expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title);
+ expect(getTextFromNode(component, '.state-description')).toEqual(component.states.loading.description);
+ expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText);
+ });
+
+ it('should show the unableToConnect state', () => {
+ const component = createComponent({
+ selectedState: 'unableToConnect',
+ settingsPath: statePaths.settingsPath,
+ documentationPath: statePaths.documentationPath,
+ });
+
+ expect(component.$el.querySelector('svg')).toBeDefined();
+ expect(getTextFromNode(component, '.state-title')).toEqual(component.states.unableToConnect.title);
+ expect(component.$el.querySelector('.state-description a')).toBeDefined();
+ expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.unableToConnect.buttonText);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
new file mode 100644
index 00000000000..20c1e6a0005
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_store_spec.js
@@ -0,0 +1,24 @@
+import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import MonitoringMock, { deploymentData } from './mock_data';
+
+describe('MonitoringStore', () => {
+ this.store = new MonitoringStore();
+ this.store.storeMetrics(MonitoringMock.data);
+
+ it('contains one group that contains two queries sorted by priority in one row', () => {
+ expect(this.store.groups).toBeDefined();
+ expect(this.store.groups.length).toEqual(1);
+ expect(this.store.groups[0].metrics.length).toEqual(1);
+ });
+
+ it('gets the metrics count for every group', () => {
+ expect(this.store.getMetricsCount()).toEqual(2);
+ });
+
+ it('contains deployment data', () => {
+ this.store.storeDeploymentData(deploymentData);
+ expect(this.store.deploymentData).toBeDefined();
+ expect(this.store.deploymentData.length).toEqual(3);
+ expect(typeof this.store.deploymentData[0]).toEqual('object');
+ });
+});
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
deleted file mode 100644
index 25578bf1c6e..00000000000
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import 'jquery';
-import PrometheusGraph from '~/monitoring/prometheus_graph';
-import { prometheusMockData } from './prometheus_mock_data';
-
-describe('PrometheusGraph', () => {
- const fixtureName = 'environments/metrics/metrics.html.raw';
- const prometheusGraphContainer = '.prometheus-graph';
- const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
-
- preloadFixtures(fixtureName);
-
- beforeEach(() => {
- loadFixtures(fixtureName);
- $('.prometheus-container').data('has-metrics', 'true');
- this.prometheusGraph = new PrometheusGraph();
- const self = this;
- const fakeInit = (metricsResponse) => {
- self.prometheusGraph.transformData(metricsResponse);
- self.prometheusGraph.createGraph();
- };
- spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit);
- });
-
- it('initializes graph properties', () => {
- // Test for the measurements
- expect(this.prometheusGraph.margin).toBeDefined();
- expect(this.prometheusGraph.marginLabelContainer).toBeDefined();
- expect(this.prometheusGraph.originalWidth).toBeDefined();
- expect(this.prometheusGraph.originalHeight).toBeDefined();
- expect(this.prometheusGraph.height).toBeDefined();
- expect(this.prometheusGraph.width).toBeDefined();
- expect(this.prometheusGraph.backOffRequestCounter).toBeDefined();
- // Test for the graph properties (colors, radius, etc.)
- expect(this.prometheusGraph.graphSpecificProperties).toBeDefined();
- expect(this.prometheusGraph.commonGraphProperties).toBeDefined();
- });
-
- it('transforms the data', () => {
- this.prometheusGraph.init(prometheusMockData.metrics);
- Object.keys(this.prometheusGraph.graphSpecificProperties, (key) => {
- const graphProps = this.prometheusGraph.graphSpecificProperties[key];
- expect(graphProps.data).toBeDefined();
- expect(graphProps.data.length).toBe(121);
- });
- });
-
- it('creates two graphs', () => {
- this.prometheusGraph.init(prometheusMockData.metrics);
- expect($(prometheusGraphContainer).length).toBe(2);
- });
-
- describe('Graph contents', () => {
- beforeEach(() => {
- this.prometheusGraph.init(prometheusMockData.metrics);
- });
-
- it('has axis, an area, a line and a overlay', () => {
- const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent();
- expect($graphContainer.find('.x-axis')).toBeDefined();
- expect($graphContainer.find('.y-axis')).toBeDefined();
- expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined();
- expect($graphContainer.find('.metric-line')).toBeDefined();
- expect($graphContainer.find('.metric-area')).toBeDefined();
- });
-
- it('has legends, labels and an extra axis that labels the metrics', () => {
- const $prometheusGraphContents = $(prometheusGraphContents);
- const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent();
- expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined();
- expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
- expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
- expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
- expect($axisLabelContainer.find('rect').length).toBe(3);
- expect($axisLabelContainer.find('text').length).toBe(4);
- });
- });
-});
-
-describe('PrometheusGraphs UX states', () => {
- const fixtureName = 'environments/metrics/metrics.html.raw';
- preloadFixtures(fixtureName);
-
- beforeEach(() => {
- loadFixtures(fixtureName);
- this.prometheusGraph = new PrometheusGraph();
- });
-
- it('shows a specified state', () => {
- this.prometheusGraph.state = '.js-getting-started';
- this.prometheusGraph.updateState();
- const $state = $('.js-getting-started');
- expect($state).toBeDefined();
- expect($('.state-title', $state)).toBeDefined();
- expect($('.state-svg', $state)).toBeDefined();
- expect($('.state-description', $state)).toBeDefined();
- expect($('.state-button', $state)).toBeDefined();
- });
-});
diff --git a/spec/javascripts/monitoring/prometheus_mock_data.js b/spec/javascripts/monitoring/prometheus_mock_data.js
deleted file mode 100644
index 1cdc14faaa8..00000000000
--- a/spec/javascripts/monitoring/prometheus_mock_data.js
+++ /dev/null
@@ -1,1014 +0,0 @@
-/* eslint-disable import/prefer-default-export*/
-export const prometheusMockData = {
- status: 200,
- metrics: {
- success: true,
- metrics: {
- memory_values: [
- {
- metric: {
- },
- values: [
- [
- 1488462917.256,
- '10.12890625',
- ],
- [
- 1488462977.256,
- '10.140625',
- ],
- [
- 1488463037.256,
- '10.140625',
- ],
- [
- 1488463097.256,
- '10.14453125',
- ],
- [
- 1488463157.256,
- '10.1484375',
- ],
- [
- 1488463217.256,
- '10.15625',
- ],
- [
- 1488463277.256,
- '10.15625',
- ],
- [
- 1488463337.256,
- '10.15625',
- ],
- [
- 1488463397.256,
- '10.1640625',
- ],
- [
- 1488463457.256,
- '10.171875',
- ],
- [
- 1488463517.256,
- '10.171875',
- ],
- [
- 1488463577.256,
- '10.171875',
- ],
- [
- 1488463637.256,
- '10.18359375',
- ],
- [
- 1488463697.256,
- '10.1953125',
- ],
- [
- 1488463757.256,
- '10.203125',
- ],
- [
- 1488463817.256,
- '10.20703125',
- ],
- [
- 1488463877.256,
- '10.20703125',
- ],
- [
- 1488463937.256,
- '10.20703125',
- ],
- [
- 1488463997.256,
- '10.20703125',
- ],
- [
- 1488464057.256,
- '10.2109375',
- ],
- [
- 1488464117.256,
- '10.2109375',
- ],
- [
- 1488464177.256,
- '10.2109375',
- ],
- [
- 1488464237.256,
- '10.2109375',
- ],
- [
- 1488464297.256,
- '10.21484375',
- ],
- [
- 1488464357.256,
- '10.22265625',
- ],
- [
- 1488464417.256,
- '10.22265625',
- ],
- [
- 1488464477.256,
- '10.2265625',
- ],
- [
- 1488464537.256,
- '10.23046875',
- ],
- [
- 1488464597.256,
- '10.23046875',
- ],
- [
- 1488464657.256,
- '10.234375',
- ],
- [
- 1488464717.256,
- '10.234375',
- ],
- [
- 1488464777.256,
- '10.234375',
- ],
- [
- 1488464837.256,
- '10.234375',
- ],
- [
- 1488464897.256,
- '10.234375',
- ],
- [
- 1488464957.256,
- '10.234375',
- ],
- [
- 1488465017.256,
- '10.23828125',
- ],
- [
- 1488465077.256,
- '10.23828125',
- ],
- [
- 1488465137.256,
- '10.2421875',
- ],
- [
- 1488465197.256,
- '10.2421875',
- ],
- [
- 1488465257.256,
- '10.2421875',
- ],
- [
- 1488465317.256,
- '10.2421875',
- ],
- [
- 1488465377.256,
- '10.2421875',
- ],
- [
- 1488465437.256,
- '10.2421875',
- ],
- [
- 1488465497.256,
- '10.2421875',
- ],
- [
- 1488465557.256,
- '10.2421875',
- ],
- [
- 1488465617.256,
- '10.2421875',
- ],
- [
- 1488465677.256,
- '10.2421875',
- ],
- [
- 1488465737.256,
- '10.2421875',
- ],
- [
- 1488465797.256,
- '10.24609375',
- ],
- [
- 1488465857.256,
- '10.25',
- ],
- [
- 1488465917.256,
- '10.25390625',
- ],
- [
- 1488465977.256,
- '9.98828125',
- ],
- [
- 1488466037.256,
- '9.9921875',
- ],
- [
- 1488466097.256,
- '9.9921875',
- ],
- [
- 1488466157.256,
- '9.99609375',
- ],
- [
- 1488466217.256,
- '10',
- ],
- [
- 1488466277.256,
- '10.00390625',
- ],
- [
- 1488466337.256,
- '10.0078125',
- ],
- [
- 1488466397.256,
- '10.01171875',
- ],
- [
- 1488466457.256,
- '10.0234375',
- ],
- [
- 1488466517.256,
- '10.02734375',
- ],
- [
- 1488466577.256,
- '10.02734375',
- ],
- [
- 1488466637.256,
- '10.03125',
- ],
- [
- 1488466697.256,
- '10.03125',
- ],
- [
- 1488466757.256,
- '10.03125',
- ],
- [
- 1488466817.256,
- '10.03125',
- ],
- [
- 1488466877.256,
- '10.03125',
- ],
- [
- 1488466937.256,
- '10.03125',
- ],
- [
- 1488466997.256,
- '10.03125',
- ],
- [
- 1488467057.256,
- '10.0390625',
- ],
- [
- 1488467117.256,
- '10.0390625',
- ],
- [
- 1488467177.256,
- '10.04296875',
- ],
- [
- 1488467237.256,
- '10.05078125',
- ],
- [
- 1488467297.256,
- '10.05859375',
- ],
- [
- 1488467357.256,
- '10.06640625',
- ],
- [
- 1488467417.256,
- '10.06640625',
- ],
- [
- 1488467477.256,
- '10.0703125',
- ],
- [
- 1488467537.256,
- '10.07421875',
- ],
- [
- 1488467597.256,
- '10.0859375',
- ],
- [
- 1488467657.256,
- '10.0859375',
- ],
- [
- 1488467717.256,
- '10.09765625',
- ],
- [
- 1488467777.256,
- '10.1015625',
- ],
- [
- 1488467837.256,
- '10.10546875',
- ],
- [
- 1488467897.256,
- '10.10546875',
- ],
- [
- 1488467957.256,
- '10.125',
- ],
- [
- 1488468017.256,
- '10.13671875',
- ],
- [
- 1488468077.256,
- '10.1484375',
- ],
- [
- 1488468137.256,
- '10.15625',
- ],
- [
- 1488468197.256,
- '10.16796875',
- ],
- [
- 1488468257.256,
- '10.171875',
- ],
- [
- 1488468317.256,
- '10.171875',
- ],
- [
- 1488468377.256,
- '10.171875',
- ],
- [
- 1488468437.256,
- '10.171875',
- ],
- [
- 1488468497.256,
- '10.171875',
- ],
- [
- 1488468557.256,
- '10.171875',
- ],
- [
- 1488468617.256,
- '10.171875',
- ],
- [
- 1488468677.256,
- '10.17578125',
- ],
- [
- 1488468737.256,
- '10.17578125',
- ],
- [
- 1488468797.256,
- '10.265625',
- ],
- [
- 1488468857.256,
- '10.19921875',
- ],
- [
- 1488468917.256,
- '10.19921875',
- ],
- [
- 1488468977.256,
- '10.19921875',
- ],
- [
- 1488469037.256,
- '10.19921875',
- ],
- [
- 1488469097.256,
- '10.19921875',
- ],
- [
- 1488469157.256,
- '10.203125',
- ],
- [
- 1488469217.256,
- '10.43359375',
- ],
- [
- 1488469277.256,
- '10.20703125',
- ],
- [
- 1488469337.256,
- '10.2109375',
- ],
- [
- 1488469397.256,
- '10.22265625',
- ],
- [
- 1488469457.256,
- '10.21484375',
- ],
- [
- 1488469517.256,
- '10.21484375',
- ],
- [
- 1488469577.256,
- '10.21484375',
- ],
- [
- 1488469637.256,
- '10.22265625',
- ],
- [
- 1488469697.256,
- '10.234375',
- ],
- [
- 1488469757.256,
- '10.234375',
- ],
- [
- 1488469817.256,
- '10.234375',
- ],
- [
- 1488469877.256,
- '10.2421875',
- ],
- [
- 1488469937.256,
- '10.25',
- ],
- [
- 1488469997.256,
- '10.25390625',
- ],
- [
- 1488470057.256,
- '10.26171875',
- ],
- [
- 1488470117.256,
- '10.2734375',
- ],
- ],
- },
- ],
- memory_current: [
- {
- metric: {
- },
- value: [
- 1488470117.737,
- '10.2734375',
- ],
- },
- ],
- cpu_values: [
- {
- metric: {
- },
- values: [
- [
- 1488462918.15,
- '0.0002996458625058103',
- ],
- [
- 1488462978.15,
- '0.0002652382333333314',
- ],
- [
- 1488463038.15,
- '0.0003485461333333421',
- ],
- [
- 1488463098.15,
- '0.0003420421999999886',
- ],
- [
- 1488463158.15,
- '0.00023107150000001297',
- ],
- [
- 1488463218.15,
- '0.00030463981666664826',
- ],
- [
- 1488463278.15,
- '0.0002477177833333677',
- ],
- [
- 1488463338.15,
- '0.00026936656666665115',
- ],
- [
- 1488463398.15,
- '0.000406264750000022',
- ],
- [
- 1488463458.15,
- '0.00029592802026561453',
- ],
- [
- 1488463518.15,
- '0.00023426999683316343',
- ],
- [
- 1488463578.15,
- '0.0003057080666666915',
- ],
- [
- 1488463638.15,
- '0.0003408470500000149',
- ],
- [
- 1488463698.15,
- '0.00025497336666665166',
- ],
- [
- 1488463758.15,
- '0.0003009282833333534',
- ],
- [
- 1488463818.15,
- '0.0003119383499999924',
- ],
- [
- 1488463878.15,
- '0.00028719019999998705',
- ],
- [
- 1488463938.15,
- '0.000327864749999988',
- ],
- [
- 1488463998.15,
- '0.0002514917333333422',
- ],
- [
- 1488464058.15,
- '0.0003614651166666742',
- ],
- [
- 1488464118.15,
- '0.0003221668000000122',
- ],
- [
- 1488464178.15,
- '0.00023323083333330884',
- ],
- [
- 1488464238.15,
- '0.00028531499475009274',
- ],
- [
- 1488464298.15,
- '0.0002627695294921391',
- ],
- [
- 1488464358.15,
- '0.00027145463333333453',
- ],
- [
- 1488464418.15,
- '0.00025669488333335266',
- ],
- [
- 1488464478.15,
- '0.00022307761666665965',
- ],
- [
- 1488464538.15,
- '0.0003307265833333517',
- ],
- [
- 1488464598.15,
- '0.0002817050666666709',
- ],
- [
- 1488464658.15,
- '0.00022357458333332285',
- ],
- [
- 1488464718.15,
- '0.00032648590000000275',
- ],
- [
- 1488464778.15,
- '0.00028410750000000816',
- ],
- [
- 1488464838.15,
- '0.0003038076999999954',
- ],
- [
- 1488464898.15,
- '0.00037568226666667335',
- ],
- [
- 1488464958.15,
- '0.00020160354999999202',
- ],
- [
- 1488465018.15,
- '0.0003229403333333399',
- ],
- [
- 1488465078.15,
- '0.00033516069999999236',
- ],
- [
- 1488465138.15,
- '0.0003365978333333371',
- ],
- [
- 1488465198.15,
- '0.00020262178333331585',
- ],
- [
- 1488465258.15,
- '0.00040567498333331876',
- ],
- [
- 1488465318.15,
- '0.00029114155000001436',
- ],
- [
- 1488465378.15,
- '0.0002498841000000122',
- ],
- [
- 1488465438.15,
- '0.00027296763333331715',
- ],
- [
- 1488465498.15,
- '0.0002958794000000135',
- ],
- [
- 1488465558.15,
- '0.0002922354666666867',
- ],
- [
- 1488465618.15,
- '0.00034186624999999653',
- ],
- [
- 1488465678.15,
- '0.0003397984166666627',
- ],
- [
- 1488465738.15,
- '0.0002658284166666469',
- ],
- [
- 1488465798.15,
- '0.00026221139999999346',
- ],
- [
- 1488465858.15,
- '0.00029467960000001034',
- ],
- [
- 1488465918.15,
- '0.0002634141333333358',
- ],
- [
- 1488465978.15,
- '0.0003202958333333209',
- ],
- [
- 1488466038.15,
- '0.00037890760000000394',
- ],
- [
- 1488466098.15,
- '0.00023453356666666518',
- ],
- [
- 1488466158.15,
- '0.0002866827333333433',
- ],
- [
- 1488466218.15,
- '0.0003335935499999998',
- ],
- [
- 1488466278.15,
- '0.00022787131666666125',
- ],
- [
- 1488466338.15,
- '0.00033821938333333064',
- ],
- [
- 1488466398.15,
- '0.00029233375000001043',
- ],
- [
- 1488466458.15,
- '0.00026562758333333514',
- ],
- [
- 1488466518.15,
- '0.0003142600999999819',
- ],
- [
- 1488466578.15,
- '0.00027392178333333444',
- ],
- [
- 1488466638.15,
- '0.00028178598333334173',
- ],
- [
- 1488466698.15,
- '0.0002463400666666911',
- ],
- [
- 1488466758.15,
- '0.00040234373333332125',
- ],
- [
- 1488466818.15,
- '0.00023677453333332822',
- ],
- [
- 1488466878.15,
- '0.00030852703333333523',
- ],
- [
- 1488466938.15,
- '0.0003582272833333455',
- ],
- [
- 1488466998.15,
- '0.0002176380833332973',
- ],
- [
- 1488467058.15,
- '0.00026180203333335447',
- ],
- [
- 1488467118.15,
- '0.00027862966666667436',
- ],
- [
- 1488467178.15,
- '0.0002769731166666567',
- ],
- [
- 1488467238.15,
- '0.0002832899166666477',
- ],
- [
- 1488467298.15,
- '0.0003446533500000311',
- ],
- [
- 1488467358.15,
- '0.0002691345999999761',
- ],
- [
- 1488467418.15,
- '0.000284919933333357',
- ],
- [
- 1488467478.15,
- '0.0002396026166666528',
- ],
- [
- 1488467538.15,
- '0.00035625295000002075',
- ],
- [
- 1488467598.15,
- '0.00036759816666664946',
- ],
- [
- 1488467658.15,
- '0.00030326608333333855',
- ],
- [
- 1488467718.15,
- '0.00023584972418043393',
- ],
- [
- 1488467778.15,
- '0.00025744508892115107',
- ],
- [
- 1488467838.15,
- '0.00036737541666663395',
- ],
- [
- 1488467898.15,
- '0.00034325741666666094',
- ],
- [
- 1488467958.15,
- '0.00026390046666667407',
- ],
- [
- 1488468018.15,
- '0.0003302534500000102',
- ],
- [
- 1488468078.15,
- '0.00035243794999999527',
- ],
- [
- 1488468138.15,
- '0.00020149738333333407',
- ],
- [
- 1488468198.15,
- '0.0003183469666666679',
- ],
- [
- 1488468258.15,
- '0.0003835329166666845',
- ],
- [
- 1488468318.15,
- '0.0002485075333333124',
- ],
- [
- 1488468378.15,
- '0.0003011457166666768',
- ],
- [
- 1488468438.15,
- '0.00032242785497684965',
- ],
- [
- 1488468498.15,
- '0.0002659713747457531',
- ],
- [
- 1488468558.15,
- '0.0003476860333333202',
- ],
- [
- 1488468618.15,
- '0.00028336403333334794',
- ],
- [
- 1488468678.15,
- '0.00017132354999998728',
- ],
- [
- 1488468738.15,
- '0.0003001915833333276',
- ],
- [
- 1488468798.15,
- '0.0003025715666666725',
- ],
- [
- 1488468858.15,
- '0.0003012370166666815',
- ],
- [
- 1488468918.15,
- '0.00030203619999997025',
- ],
- [
- 1488468978.15,
- '0.0002804355000000314',
- ],
- [
- 1488469038.15,
- '0.00033194884999998564',
- ],
- [
- 1488469098.15,
- '0.00025201496666665455',
- ],
- [
- 1488469158.15,
- '0.0002777531500000189',
- ],
- [
- 1488469218.15,
- '0.0003314885833333392',
- ],
- [
- 1488469278.15,
- '0.0002234891422095589',
- ],
- [
- 1488469338.15,
- '0.000349117355867791',
- ],
- [
- 1488469398.15,
- '0.0004036731333333303',
- ],
- [
- 1488469458.15,
- '0.00024553911666667835',
- ],
- [
- 1488469518.15,
- '0.0003056456833333184',
- ],
- [
- 1488469578.15,
- '0.0002618737166666681',
- ],
- [
- 1488469638.15,
- '0.00022972643333331414',
- ],
- [
- 1488469698.15,
- '0.0003713522500000307',
- ],
- [
- 1488469758.15,
- '0.00018322576666666515',
- ],
- [
- 1488469818.15,
- '0.00034534762753952466',
- ],
- [
- 1488469878.15,
- '0.00028200510008501677',
- ],
- [
- 1488469938.15,
- '0.0002773708499999768',
- ],
- [
- 1488469998.15,
- '0.00027547160000001013',
- ],
- [
- 1488470058.15,
- '0.00031713610000000023',
- ],
- [
- 1488470118.15,
- '0.00035276853333332525',
- ],
- ],
- },
- ],
- cpu_current: [
- {
- metric: {
- },
- value: [
- 1488470118.566,
- '0.00035276853333332525',
- ],
- },
- ],
- last_update: '2017-03-02T15:55:18.981Z',
- },
- },
-};
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 5ece4ed080b..2c096ed08a8 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -523,6 +523,51 @@ import '~/notes';
});
});
+ describe('postComment with Slash commands', () => {
+ const sampleComment = '/assign @root\n/award :100:';
+ const note = {
+ commands_changes: {
+ assignee_id: 1,
+ emoji_award: '100'
+ },
+ errors: {
+ commands_only: ['Commands applied']
+ },
+ valid: false
+ };
+ let $form;
+ let $notesContainer;
+
+ beforeEach(() => {
+ this.notes = new Notes('', []);
+ window.gon.current_username = 'root';
+ window.gon.current_user_fullname = 'Administrator';
+ gl.awardsHandler = {
+ addAwardToEmojiBar: () => {},
+ scrollToAwards: () => {}
+ };
+ gl.GfmAutoComplete = {
+ dataSources: {
+ commands: '/root/test-project/autocomplete_sources/commands'
+ }
+ };
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').val(sampleComment);
+ });
+
+ it('should remove slash command placeholder when comment with slash commands is done posting', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
+ $('.js-comment-button').click();
+
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+ deferred.resolve(note);
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ });
+ });
+
describe('update comment with script tags', () => {
const sampleComment = '<script></script>';
const updatedComment = '<script></script>';
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
new file mode 100644
index 00000000000..f90e0093d25
--- /dev/null
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -0,0 +1,26 @@
+import OAuthRememberMe from '~/oauth_remember_me';
+
+describe('OAuthRememberMe', () => {
+ preloadFixtures('static/oauth_remember_me.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/oauth_remember_me.html.raw');
+
+ new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
+ });
+
+ it('adds the "remember_me" query parameter to all OAuth login buttons', () => {
+ $('#oauth-container #remember_me').click();
+
+ expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/?remember_me=1');
+ expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/?remember_me=1');
+ });
+
+ it('removes the "remember_me" query parameter from all OAuth login buttons', () => {
+ $('#oauth-container #remember_me').click();
+ $('#oauth-container #remember_me').click();
+
+ expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/');
+ expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/');
+ });
+});
diff --git a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
new file mode 100644
index 00000000000..5b316b319a5
--- /dev/null
+++ b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
@@ -0,0 +1,145 @@
+import {
+ setupPipelineVariableList,
+ insertRow,
+ removeRow,
+} from '~/pipeline_schedules/setup_pipeline_variable_list';
+
+describe('Pipeline Variable List', () => {
+ let $markup;
+
+ describe('insertRow', () => {
+ it('should insert another row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input>
+ <textarea></textarea>
+ </li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should clear `data-is-persisted` on cloned row', () => {
+ $markup = $(`<div>
+ <li class="js-row" data-is-persisted="true"></li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ const $lastRow = $markup.find('.js-row').last();
+ expect($lastRow.attr('data-is-persisted')).toBe(undefined);
+ });
+
+ it('should clear inputs on cloned row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input value="foo">
+ <textarea>bar</textarea>
+ </li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ const $lastRow = $markup.find('.js-row').last();
+ expect($lastRow.find('input').val()).toBe('');
+ expect($lastRow.find('textarea').val()).toBe('');
+ });
+ });
+
+ describe('removeRow', () => {
+ it('should remove dynamic row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input>
+ <textarea></textarea>
+ </li>
+ </div>`);
+
+ removeRow($markup.find('.js-row'));
+
+ expect($markup.find('.js-row').length).toBe(0);
+ });
+
+ it('should hide and mark to destroy with already persisted rows', () => {
+ $markup = $(`<div>
+ <li class="js-row" data-is-persisted="true">
+ <input class="js-destroy-input">
+ </li>
+ </div>`);
+
+ const $row = $markup.find('.js-row');
+ removeRow($row);
+
+ expect($row.find('.js-destroy-input').val()).toBe('1');
+ expect($markup.find('.js-row').length).toBe(1);
+ });
+ });
+
+ describe('setupPipelineVariableList', () => {
+ beforeEach(() => {
+ $markup = $(`<form>
+ <li class="js-row">
+ <input class="js-user-input" name="schedule[variables_attributes][][key]">
+ <textarea class="js-user-input" name="schedule[variables_attributes][][value]"></textarea>
+ <button class="js-row-remove-button"></button>
+ <button class="js-row-add-button"></button>
+ </li>
+ </form>`);
+
+ setupPipelineVariableList($markup);
+ });
+
+ it('should remove the row when clicking the remove button', () => {
+ $markup.find('.js-row-remove-button').trigger('click');
+
+ expect($markup.find('.js-row').length).toBe(0);
+ });
+
+ it('should add another row when editing the last rows key input', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('input.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should add another row when editing the last rows value textarea', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('textarea.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should remove empty row after blurring', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('input.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+
+ $row.find('input.js-user-input')
+ .val('')
+ .trigger('input')
+ .trigger('blur');
+
+ expect($markup.find('.js-row').length).toBe(1);
+ });
+
+ it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
+ const $row = $markup.find('.js-row');
+ expect($row.find('input').attr('name')).toBe('schedule[variables_attributes][][key]');
+ expect($row.find('textarea').attr('name')).toBe('schedule[variables_attributes][][value]');
+
+ $markup.filter('form').submit();
+
+ expect($row.find('input').attr('name')).toBe('');
+ expect($row.find('textarea').attr('name')).toBe('');
+ });
+ });
+});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index 0a32797c3e2..a53e8a94d89 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,8 +1,7 @@
import AccessorUtilities from '~/lib/utils/accessor';
+import SigninTabsMemoizer from '~/signin_tabs_memoizer';
-import '~/signin_tabs_memoizer';
-
-((global) => {
+(() => {
describe('SigninTabsMemoizer', () => {
const fixtureTemplate = 'static/signin_tabs.html.raw';
const tabSelector = 'ul.nav-tabs';
@@ -10,7 +9,7 @@ import '~/signin_tabs_memoizer';
let memo;
function createMemoizer() {
- memo = new global.ActiveTabMemoizer({
+ memo = new SigninTabsMemoizer({
currentTabKey,
tabSelector,
});
@@ -78,7 +77,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = false;
- global.ActiveTabMemoizer.prototype.saveData.call(memo);
+ SigninTabsMemoizer.prototype.saveData.call(memo);
});
it('should not call .setItem', () => {
@@ -92,7 +91,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = true;
- global.ActiveTabMemoizer.prototype.saveData.call(memo, value);
+ SigninTabsMemoizer.prototype.saveData.call(memo, value);
});
it('should call .setItem', () => {
@@ -117,7 +116,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = false;
- readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
+ readData = SigninTabsMemoizer.prototype.readData.call(memo);
});
it('should not call .getItem and should return `null`', () => {
@@ -130,7 +129,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = true;
- readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
+ readData = SigninTabsMemoizer.prototype.readData.call(memo);
});
it('should call .getItem and return the localStorage value', () => {
@@ -140,4 +139,4 @@ import '~/signin_tabs_memoizer';
});
});
});
-})(window);
+})();
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index cd74aba4a4e..fd492159081 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,4 +1,4 @@
-import '~/todos';
+import Todos from '~/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
@@ -9,7 +9,7 @@ describe('Todos', () => {
loadFixtures('todos/todos.html.raw');
todoItem = document.querySelector('.todos-list .todo');
- return new gl.Todos();
+ return new Todos();
});
describe('goToTodoUrl', () => {
diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js
index c2eaea7c2ed..82714cb69bd 100644
--- a/spec/javascripts/visibility_select_spec.js
+++ b/spec/javascripts/visibility_select_spec.js
@@ -1,8 +1,6 @@
-import '~/visibility_select';
+import VisibilitySelect from '~/visibility_select';
(() => {
- const VisibilitySelect = gl.VisibilitySelect;
-
describe('VisibilitySelect', function () {
const lockedElement = document.createElement('div');
lockedElement.dataset.helpBlock = 'lockedHelpBlock';
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 4b6f171c8d6..647b59520f8 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
@@ -76,28 +76,6 @@ describe('MRWidgetPipeline', () => {
el = vm.$el;
});
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('without a pipeline', () => {
- beforeEach(() => {
- vm.mr = { pipeline: null };
- });
-
- it('should render message with spinner', (done) => {
- Vue.nextTick()
- .then(() => {
- expect(el.querySelector('.pipeline-id')).toBe(null);
- expect(el.innerText.trim()).toBe('Waiting for pipeline...');
- expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1);
- done();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
it('should render template elements correctly', () => {
expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
@@ -115,47 +93,39 @@ describe('MRWidgetPipeline', () => {
it('should list single stage', (done) => {
pipeline.details.stages.splice(0, 1);
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
- expect(el.innerText).toContain('with stage');
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
+ expect(el.innerText).toContain('with stage');
+ done();
+ });
});
it('should not have stages when there is no stage', (done) => {
vm.mr.pipeline.details.stages = [];
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
+ done();
+ });
});
it('should not have coverage text when pipeline has no coverage info', (done) => {
vm.mr.pipeline.coverage = null;
- Vue.nextTick()
- .then(() => {
- expect(el.querySelector('.js-mr-coverage')).toEqual(null);
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelector('.js-mr-coverage')).toEqual(null);
+ done();
+ });
});
it('should show CI error when there is a CI error', (done) => {
vm.mr.ciStatus = null;
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
- expect(el.innerText).toContain('Could not connect to the CI server');
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
+ expect(el.innerText).toContain('Could not connect to the CI server');
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 895e1c585b4..b0b78e34e0f 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -1,150 +1,188 @@
import Vue from 'vue';
import paginationComp from '~/vue_shared/components/table_pagination.vue';
-import '~/lib/utils/common_utils';
describe('Pagination component', () => {
let component;
let PaginationComponent;
-
- const changeChanges = {
- one: '',
- };
-
- const change = (one) => {
- changeChanges.one = one;
- };
+ let spy;
+ let mountComponet;
beforeEach(() => {
+ spy = jasmine.createSpy('spy');
PaginationComponent = Vue.extend(paginationComp);
- });
-
- it('should render and start at page 1', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 2,
- previousPage: '',
- },
- change,
- },
- }).$mount();
- expect(component.$el.classList).toContain('gl-pagination');
-
- component.changePage({ target: { innerText: '1' } });
-
- expect(changeChanges.one).toEqual(1);
+ mountComponet = function (props) {
+ return new PaginationComponent({
+ propsData: props,
+ }).$mount();
+ };
});
- it('should go to the previous page', () => {
- component = new PaginationComponent({
- propsData: {
+ describe('render', () => {
+ describe('prev button', () => {
+ it('should be disabled and non clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 2,
+ page: 1,
+ perPage: 20,
+ previousPage: NaN,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
+ ).toEqual(true);
+
+ component.$el.querySelector('.js-previous-button a').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be enabled and clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-previous-button a').click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('first button', () => {
+ it('should call the change callback with the first page', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-first-button a');
+
+ expect(button.textContent.trim()).toEqual('« First');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('last button', () => {
+ it('should call the change callback with the last page', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-last-button a');
+
+ expect(button.textContent.trim()).toEqual('Last »');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(5);
+ });
+ });
+
+ describe('next button', () => {
+ it('should be disabled and non clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 5,
+ page: 5,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-next-button').textContent.trim(),
+ ).toEqual('Next');
+
+ component.$el.querySelector('.js-next-button a').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be enabled and clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-next-button a').click();
+
+ expect(spy).toHaveBeenCalledWith(4);
+ });
+ });
+
+ describe('numbered buttons', () => {
+ it('should render 5 pages', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelectorAll('.page').length).toEqual(5);
+ });
+ });
+
+ it('should render the spread operator', () => {
+ component = mountComponet({
pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
totalPages: 10,
- nextPage: 3,
- previousPage: 1,
},
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Prev' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-
- it('should go to the next page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Next' } });
-
- expect(changeChanges.one).toEqual(5);
- });
-
- it('should go to the last page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Last »' } });
-
- expect(changeChanges.one).toEqual(10);
- });
-
- it('should go to the first page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: '« First' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-
- it('should do nothing', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 2,
- previousPage: '',
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: '...' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-});
-
-describe('paramHelper', () => {
- afterEach(() => {
- window.history.pushState({}, null, '');
- });
-
- it('can parse url parameters correctly', () => {
- window.history.pushState({}, null, '?scope=all&p=2');
-
- const scope = gl.utils.getParameterByName('scope');
- const p = gl.utils.getParameterByName('p');
-
- expect(scope).toEqual('all');
- expect(p).toEqual('2');
- });
-
- it('returns null if param not in url', () => {
- window.history.pushState({}, null, '?p=2');
-
- const scope = gl.utils.getParameterByName('scope');
- const p = gl.utils.getParameterByName('p');
+ change: spy,
+ });
- expect(scope).toEqual(null);
- expect(p).toEqual('2');
+ expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
+ });
});
});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 4399c8b2025..a225b04c47e 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,9 +1,8 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */
/* global Dropzone */
/* global Mousetrap */
-/* global ZenMode */
-import '~/zen_mode';
+import ZenMode from '~/zen_mode';
(function() {
var enterZen, escapeKeydown, exitZen;
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index fc67c7ec3c4..60c27bc0d3c 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -29,14 +29,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference2}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
+ .to eq urls.project_compare_url(project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
+ .to eq urls.project_compare_url(project, range.to_param)
end
it 'links to a valid short ID' do
@@ -94,7 +94,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+ expect(link).to eq urls.project_compare_url(project, from: commit1.id, to: commit2.id, only_path: true)
end
end
@@ -106,7 +106,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -141,7 +141,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -176,7 +176,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -205,7 +205,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, :repository, namespace: namespace) }
let(:range) { CommitRange.new("#{commit1.id}...master", project) }
- let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
+ let(:reference) { urls.project_compare_url(project2, from: commit1.id, to: 'master') }
before do
range.project = project2
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index c4d8d3b6682..f6893641481 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -27,7 +27,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
expect(doc.css('a').first.text).to eq commit.short_id
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+ .to eq urls.project_commit_url(project, reference)
end
end
@@ -90,7 +90,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+ expect(link).to eq urls.project_commit_url(project, reference, only_path: true)
end
end
@@ -175,13 +175,13 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, :repository, namespace: namespace) }
let(:commit) { project2.commit }
- let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
+ let(:reference) { urls.project_commit_url(project2, commit.id) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+ .to eq urls.project_commit_url(project2, commit.id)
end
it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a4bb043f8f1..b7d82c36ddd 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -88,12 +88,12 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
it 'queries the collection on the first call' do
expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
- expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original
+ expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
not_cached = reference_filter.call("look for #{reference}", { project: project })
expect_any_instance_of(Project).not_to receive(:default_issues_tracker?)
- expect_any_instance_of(Project).not_to receive(:issue_reference_pattern)
+ expect_any_instance_of(Project).not_to receive(:external_issue_reference_pattern)
cached = reference_filter.call("look for #{reference}", { project: project })
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index e5c1deb338b..a79d365d6c5 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -39,13 +39,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
let(:reference) { "##{issue.iid}" }
- it 'ignores valid references when using non-default tracker' do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
-
- exp = act = "Issue #{reference}"
- expect(reference_filter(act).to_html).to eq exp
- end
-
it 'links to a valid reference' do
doc = reference_filter("Fixed #{reference}")
@@ -340,24 +333,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
.to eq({ project => { issue.iid => issue } })
end
end
-
- context 'using an external issue tracker' do
- it 'returns a Hash containing the issues per project' do
- doc = Nokogiri::HTML.fragment('')
- filter = described_class.new(doc, project: project)
-
- expect(project).to receive(:default_issues_tracker?).and_return(false)
-
- expect(filter).to receive(:projects_per_reference)
- .and_return({ project.path_with_namespace => project })
-
- expect(filter).to receive(:references_per_project)
- .and_return({ project.path_with_namespace => Set.new([1]) })
-
- expect(filter.issues_per_project[project][1])
- .to be_an_instance_of(ExternalIssue)
- end
- end
end
describe '.references_in' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index cb3cf982351..8daef3ca691 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -45,7 +45,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
+ expect(link).to eq urls.project_issues_path(project, label_name: label.name)
end
context 'project that does not exist referenced' do
@@ -73,7 +73,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
end
it 'links with adjacent text' do
@@ -96,7 +96,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See gfm'
end
@@ -120,7 +120,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See 2fa'
end
@@ -144,7 +144,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See ?g.fm&'
end
@@ -169,7 +169,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
@@ -193,7 +193,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See 2 factor authentication'
end
@@ -217,7 +217,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See g.fm & references?'
end
@@ -250,9 +250,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{references}")
expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
- urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ urls.project_issues_url(project, label_name: bug.name),
+ urls.project_issues_url(project, label_name: feature_proposal.name),
+ urls.project_issues_url(project, label_name: technical_debt.name)
])
expect(doc.text).to eq 'See bug, feature proposal, technical debt'
end
@@ -265,9 +265,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{references}")
expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
- urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ urls.project_issues_url(project, label_name: bug.name),
+ urls.project_issues_url(project, label_name: feature_proposal.name),
+ urls.project_issues_url(project, label_name: technical_debt.name)
])
expect(doc.text).to eq 'See bug feature proposal technical debt'
end
@@ -288,7 +288,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
end
it 'links with adjacent text' do
@@ -325,7 +325,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}", project: project)
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ .project_issues_url(project, label_name: group_label.name)
expect(doc.text).to eq 'See gfm references'
end
@@ -348,7 +348,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}", project: project)
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ .project_issues_url(project, label_name: group_label.name)
expect(doc.text).to eq "See gfm references"
end
@@ -373,9 +373,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -407,9 +405,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -441,9 +437,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -477,9 +471,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(another_project, label_name: group_label.name)
end
it 'has valid color' do
@@ -514,9 +506,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(another_project, label_name: group_label.name)
end
it 'has valid color' do
@@ -550,9 +540,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project.namespace,
- project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(project, label_name: group_label.name)
end
it 'has valid color' do
@@ -584,9 +572,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project.namespace,
- project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(project, label_name: group_label.name)
end
it 'has valid color' do
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 cd91681551e..1ad329b6452 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -37,7 +37,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_merge_request_url(project.namespace, project, merge)
+ .project_merge_request_url(project, merge)
end
it 'links with adjacent text' do
@@ -95,7 +95,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+ expect(link).to eq urls.project_merge_request_url(project, merge, only_path: true)
end
end
@@ -108,8 +108,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -142,8 +141,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -176,8 +174,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -203,7 +200,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
- let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
+ let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index fe88b9cb73e..7fab5613afc 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -45,7 +45,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls
- .namespace_project_milestone_path(project.namespace, project, milestone)
+ .project_milestone_path(project, milestone)
end
context 'Integer-based references' do
@@ -53,7 +53,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
end
it 'links with adjacent text' do
@@ -76,7 +76,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
expect(doc.text).to eq 'See gfm'
end
@@ -100,7 +100,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
expect(doc.text).to eq 'See gfm references'
end
@@ -123,7 +123,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
end
it 'links with adjacent text' do
@@ -157,9 +157,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
@@ -196,9 +194,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
@@ -235,9 +231,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index e851120bc3a..9704db0c221 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -23,7 +23,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_snippet_url(project.namespace, project, snippet)
+ .project_snippet_url(project, snippet)
end
it 'links with adjacent text' do
@@ -75,7 +75,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+ expect(link).to eq urls.project_snippet_url(project, snippet, only_path: true)
end
end
@@ -89,7 +89,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -122,7 +122,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -155,7 +155,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -181,13 +181,13 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
- let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+ let(:reference) { urls.project_snippet_url(project2, snippet) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index edf3846b742..77561e00573 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -43,7 +43,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_url(project.namespace, project)
+ .to eq urls.project_url(project)
end
it 'includes a data-author attribute when there is an author' do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
new file mode 100644
index 00000000000..1eb90dc1847
--- /dev/null
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::GfmPipeline do
+ describe 'integration between parsing regular and external issue references' do
+ let(:project) { create(:redmine_project, :public) }
+
+ it 'allows to use shorthand external reference syntax for Redmine' do
+ markdown = '#12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'parses cross-project references to regular issues' do
+ other_project = create(:empty_project, :public)
+ issue = create(:issue, project: other_project)
+ markdown = issue.to_reference(project, full: true)
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
+ )
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 58e1a0c1bc1..acdd23f81f3 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -39,16 +39,6 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
end
end
-
- context 'when the project uses an external issue tracker' do
- it 'returns all nodes' do
- link = double(:link)
-
- expect(project).to receive(:external_issue_tracker).and_return(true)
-
- expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
- end
- end
end
describe '#referenced_by' do
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index af0e7855a9b..ef58ef1b0cd 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -598,8 +598,10 @@ module Ci
describe "Image and service handling" do
context "when extended docker configuration is used" do
it "returns image and service when defined" do
- config = YAML.dump({ image: { name: "ruby:2.1" },
- services: ["mysql", { name: "docker:dind", alias: "docker" }],
+ config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: ["mysql", { name: "docker:dind", alias: "docker",
+ entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }],
before_script: ["pwd"],
rspec: { script: "rspec" } })
@@ -614,8 +616,10 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
- image: { name: "ruby:2.1" },
- services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
+ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "mysql" },
+ { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }]
},
allow_failure: false,
when: "on_success",
@@ -628,8 +632,11 @@ module Ci
config = YAML.dump({ image: "ruby:2.1",
services: ["mysql"],
before_script: ["pwd"],
- rspec: { image: { name: "ruby:2.5" },
- services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } })
+ rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "postgresql", alias: "db-pg",
+ entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
+ script: "rspec" } })
config_processor = GitlabCiYamlProcessor.new(config, path)
@@ -642,8 +649,10 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
- image: { name: "ruby:2.5" },
- services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }]
+ image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] },
+ { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
@@ -869,7 +878,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key'
+ key: 'key',
+ policy: 'pull-push'
)
end
@@ -887,7 +897,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key'
+ key: 'key',
+ policy: 'pull-push'
)
end
@@ -906,7 +917,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["test/"],
untracked: false,
- key: 'local'
+ key: 'local',
+ policy: 'pull-push'
)
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index f2132d485ab..dfffef8b9b7 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ExtractsPath, lib: true do
include ExtractsPath
include RepoHelpers
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
let(:project) { double('project') }
let(:request) { double('request') }
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 878b1d6b862..8f711e02f9b 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Cache do
- let(:entry) { described_class.new(config) }
+ subject(:entry) { described_class.new(config) }
describe 'validations' do
before do
@@ -9,22 +9,44 @@ describe Gitlab::Ci::Config::Entry::Cache do
end
context 'when entry config value is correct' do
+ let(:policy) { nil }
+
let(:config) do
{ key: 'some key',
untracked: true,
- paths: ['some/path/'] }
+ paths: ['some/path/'],
+ policy: policy }
end
describe '#value' do
it 'returns hash value' do
- expect(entry.value).to eq config
+ expect(entry.value).to eq(key: 'some key', untracked: true, paths: ['some/path/'], policy: 'pull-push')
end
end
describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ it { is_expected.to be_valid }
+ end
+
+ context 'policy is pull-push' do
+ let(:policy) { 'pull-push' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'pull-push') }
+ end
+
+ context 'policy is push' do
+ let(:policy) { 'push' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'push') }
+ end
+
+ context 'policy is pull' do
+ let(:policy) { 'pull' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'pull') }
end
context 'when key is missing' do
@@ -44,12 +66,20 @@ describe Gitlab::Ci::Config::Entry::Cache do
context 'when entry value is not correct' do
describe '#errors' do
+ subject { entry.errors }
context 'when is not a hash' do
let(:config) { 'ls' }
it 'reports errors with config value' do
- expect(entry.errors)
- .to include 'cache config should be a hash'
+ is_expected.to include 'cache config should be a hash'
+ end
+ end
+
+ context 'when policy is unknown' do
+ let(:config) { { policy: "unknown" } }
+
+ it 'reports error' do
+ is_expected.to include('cache policy should be pull-push, push, or pull')
end
end
@@ -57,8 +87,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
let(:config) { { key: 1 } }
it 'reports error with descendants' do
- expect(entry.errors)
- .to include 'key config should be a string or symbol'
+ is_expected.to include 'key config should be a string or symbol'
end
end
@@ -66,8 +95,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
let(:config) { { invalid: true } }
it 'reports error with descendants' do
- expect(entry.errors)
- .to include 'cache config contains unknown keys: invalid'
+ is_expected.to include 'cache config contains unknown keys: invalid'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 293f112b2b0..1860ed79bfd 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -143,7 +143,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#cache_value' do
it 'returns cache configuration' do
expect(global.cache_value)
- .to eq(key: 'k', untracked: true, paths: ['public/'])
+ .to eq(key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push')
end
end
@@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::Entry::Global do
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'] },
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'] },
@@ -168,7 +168,7 @@ describe Gitlab::Ci::Config::Entry::Global do
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'] },
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: {},
ignore: false,
after_script: ['make clean'] }
@@ -212,7 +212,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#cache_value' do
it 'returns correct cache definition' do
- expect(global.cache_value).to eq(key: 'a')
+ expect(global.cache_value).to eq(key: 'a', policy: 'pull-push')
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index bca22e39500..1a4d9ed5517 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Config::Entry::Image do
end
context 'when configuration is a hash' do
- let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
+ let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run) } }
describe '#value' do
it 'returns image hash' do
@@ -66,7 +66,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#entrypoint' do
it "returns image's entrypoint" do
- expect(entry.entrypoint).to eq '/bin/sh'
+ expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 92cba689f47..c5cad887b64 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -109,7 +109,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'overrides global config' do
expect(entry[:image].value).to eq(name: 'some_image')
- expect(entry[:cache].value).to eq(key: 'test')
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
end
end
@@ -123,7 +123,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'uses config from global entry' do
expect(entry[:image].value).to eq 'specified'
- expect(entry[:cache].value).to eq(key: 'test')
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 7202fe525e4..9ebf947a751 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::Entry::Service do
context 'when configuration is a hash' do
let(:config) do
- { name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' }
+ { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
end
describe '#valid?' do
@@ -72,13 +72,13 @@ describe Gitlab::Ci::Config::Entry::Service do
describe '#command' do
it "returns service's command" do
- expect(entry.command).to eq 'cmd'
+ expect(entry.command).to eq %w(cmd run)
end
end
describe '#entrypoint' do
it "returns service's entrypoint" do
- expect(entry.entrypoint).to eq '/bin/sh'
+ expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index ca68010cb89..fe988266ae3 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -276,7 +276,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
context "with a cross-project URL" do
it do
- message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+ message = "Closes #{urls.project_issue_url(issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([issue2])
end
end
@@ -292,7 +292,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
context "with an invalid URL" do
it do
- message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+ message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([])
end
end
@@ -347,14 +347,14 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end
it "fetches cross-project URL references" do
- message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+ message = "Closes #{urls.project_issue_url(issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message))
.to match_array([issue, issue2])
end
it "ignores invalid cross-project URL references" do
- message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+ message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message))
.to match_array([issue])
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index a566f24f6a6..d57ffcae8e1 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -27,10 +27,23 @@ describe Gitlab::CurrentSettings do
end
it 'falls back to DB if Redis fails' do
+ db_settings = ApplicationSetting.create!(ApplicationSetting.defaults)
+
expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
- expect(ApplicationSetting).to receive(:last).and_call_original
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
- expect(current_application_settings).to be_a(ApplicationSetting)
+ expect(current_application_settings).to eq(db_settings)
+ end
+
+ it 'creates default ApplicationSettings if none are present' do
+ expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
+
+ settings = current_application_settings
+
+ expect(settings).to be_a(ApplicationSetting)
+ expect(settings).to be_persisted
+ expect(settings).to have_attributes(ApplicationSetting.defaults)
end
context 'with migrations pending' do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 5653cfee686..8813f129ef5 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
before do
allow(migration).to receive(:say)
+ TestEnv.clean_test_path
end
def migration_namespace(namespace)
@@ -153,6 +154,30 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
end
end
+ describe '#perform_rename' do
+ describe 'for namespaces' do
+ let(:namespace) { create(:namespace, path: 'the-path') }
+ it 'renames the path' do
+ subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
+
+ expect(namespace.reload.path).to eq('renamed')
+ end
+
+ it 'renames all the routes for the namespace' do
+ child = create(:group, path: 'child', parent: namespace)
+ project = create(:project, namespace: child, path: 'the-project')
+ other_one = create(:namespace, path: 'the-path-is-similar')
+
+ subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
+
+ expect(namespace.reload.route.path).to eq('renamed')
+ expect(child.reload.route.path).to eq('renamed/child')
+ expect(project.reload.route.path).to eq('renamed/child/the-project')
+ expect(other_one.reload.route.path).to eq('the-path-is-similar')
+ end
+ end
+ end
+
describe '#move_pages' do
it 'moves the pages directory' do
expect(subject).to receive(:move_folders)
@@ -203,4 +228,53 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
expect(File.exist?(expected_file)).to be(true)
end
end
+
+ describe '#track_rename', redis: true do
+ it 'tracks a rename in redis' do
+ key = 'rename:FakeRenameReservedPathMigrationV1:namespace'
+
+ subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
+
+ old_path, new_path = [nil, nil]
+ Gitlab::Redis.with do |redis|
+ rename_info = redis.lpop(key)
+ old_path, new_path = JSON.parse(rename_info)
+ end
+
+ expect(old_path).to eq('path/to/namespace')
+ expect(new_path).to eq('path/to/renamed')
+ end
+ end
+
+ describe '#reverts_for_type', redis: true do
+ it 'yields for each tracked rename' do
+ subject.track_rename('project', 'old_path', 'new_path')
+ subject.track_rename('project', 'old_path2', 'new_path2')
+ subject.track_rename('namespace', 'namespace_path', 'new_namespace_path')
+
+ expect { |b| subject.reverts_for_type('project', &b) }
+ .to yield_successive_args(%w(old_path2 new_path2), %w(old_path new_path))
+ expect { |b| subject.reverts_for_type('namespace', &b) }
+ .to yield_with_args('namespace_path', 'new_namespace_path')
+ end
+
+ it 'keeps the revert in redis if it failed' do
+ subject.track_rename('project', 'old_path', 'new_path')
+
+ subject.reverts_for_type('project') do
+ raise 'whatever happens, keep going!'
+ end
+
+ key = 'rename:FakeRenameReservedPathMigrationV1:project'
+ stored_renames = nil
+ rename_count = 0
+ Gitlab::Redis.with do |redis|
+ stored_renames = redis.lrange(key, 0, 1)
+ rename_count = redis.llen(key)
+ end
+
+ expect(rename_count).to eq(1)
+ expect(JSON.parse(stored_renames.first)).to eq(%w(old_path new_path))
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 8125dedd3fc..803e923b4a5 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -3,9 +3,11 @@ require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
+ let(:namespace) { create(:group, name: 'the-path') }
before do
allow(migration).to receive(:say)
+ TestEnv.clean_test_path
end
def migration_namespace(namespace)
@@ -137,8 +139,6 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
end
describe "#rename_namespace" do
- let(:namespace) { create(:group, name: 'the-path') }
-
it 'renames paths & routes for the namespace' do
expect(subject).to receive(:rename_path_for_routable)
.with(namespace)
@@ -149,11 +149,27 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
expect(namespace.reload.path).to eq('the-path0')
end
+ it 'tracks the rename' do
+ expect(subject).to receive(:track_rename)
+ .with('namespace', 'the-path', 'the-path0')
+
+ subject.rename_namespace(namespace)
+ end
+
+ it 'renames things related to the namespace' do
+ expect(subject).to receive(:rename_namespace_dependencies)
+ .with(namespace, 'the-path', 'the-path0')
+
+ subject.rename_namespace(namespace)
+ end
+ end
+
+ describe '#rename_namespace_dependencies' do
it "moves the the repository for a project in the namespace" do
create(:project, namespace: namespace, path: "the-path-project")
expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git")
- subject.rename_namespace(namespace)
+ subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
expect(File.directory?(expected_repo)).to be(true)
end
@@ -161,13 +177,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
it "moves the uploads for the namespace" do
expect(subject).to receive(:move_uploads).with("the-path", "the-path0")
- subject.rename_namespace(namespace)
+ subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
end
it "moves the pages for the namespace" do
expect(subject).to receive(:move_pages).with("the-path", "the-path0")
- subject.rename_namespace(namespace)
+ subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
end
it 'invalidates the markdown cache of related projects' do
@@ -175,13 +191,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
expect(subject).to receive(:remove_cached_html_for_projects).with([project.id])
- subject.rename_namespace(namespace)
+ subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
end
it "doesn't rename users for other namespaces" do
expect(subject).not_to receive(:rename_user)
- subject.rename_namespace(namespace)
+ subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
end
it 'renames the username of a namespace for a user' do
@@ -189,7 +205,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
expect(subject).to receive(:rename_user).with('the-path', 'the-path0')
- subject.rename_namespace(user.namespace)
+ subject.rename_namespace_dependencies(user.namespace, 'the-path', 'the-path0')
end
end
@@ -224,4 +240,50 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
subject.rename_namespaces(type: :child)
end
end
+
+ describe '#revert_renames', redis: true do
+ it 'renames the routes back to the previous values' do
+ project = create(:project, path: 'a-project', namespace: namespace)
+ subject.rename_namespace(namespace)
+
+ expect(subject).to receive(:perform_rename)
+ .with(
+ kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
+ 'the-path0',
+ 'the-path'
+ ).and_call_original
+
+ subject.revert_renames
+
+ expect(namespace.reload.path).to eq('the-path')
+ expect(namespace.reload.route.path).to eq('the-path')
+ expect(project.reload.route.path).to eq('the-path/a-project')
+ end
+
+ it 'moves the repositories back to their original place' do
+ project = create(:project, path: 'a-project', namespace: namespace)
+ project.create_repository
+ subject.rename_namespace(namespace)
+
+ expected_path = File.join(TestEnv.repos_path, 'the-path', 'a-project.git')
+
+ expect(subject).to receive(:rename_namespace_dependencies)
+ .with(
+ kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
+ 'the-path0',
+ 'the-path'
+ ).and_call_original
+
+ subject.revert_renames
+
+ expect(File.directory?(expected_path)).to be_truthy
+ end
+
+ it "doesn't break when the namespace was renamed" do
+ subject.rename_namespace(namespace)
+ namespace.update_attributes!(path: 'renamed-afterwards')
+
+ expect { subject.revert_renames }.not_to raise_error
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index 802f77ad430..0e240a5ccf1 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -3,9 +3,15 @@ require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
+ let(:project) do
+ create(:empty_project,
+ path: 'the-path',
+ namespace: create(:namespace, path: 'known-parent' ))
+ end
before do
allow(migration).to receive(:say)
+ TestEnv.clean_test_path
end
describe '#projects_for_paths' do
@@ -47,12 +53,6 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr
end
describe '#rename_project' do
- let(:project) do
- create(:empty_project,
- path: 'the-path',
- namespace: create(:namespace, path: 'known-parent' ))
- end
-
it 'renames path & route for the project' do
expect(subject).to receive(:rename_path_for_routable)
.with(project)
@@ -63,27 +63,42 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr
expect(project.reload.path).to eq('the-path0')
end
+ it 'tracks the rename' do
+ expect(subject).to receive(:track_rename)
+ .with('project', 'known-parent/the-path', 'known-parent/the-path0')
+
+ subject.rename_project(project)
+ end
+
+ it 'renames the folders for the project' do
+ expect(subject).to receive(:move_project_folders).with(project, 'known-parent/the-path', 'known-parent/the-path0')
+
+ subject.rename_project(project)
+ end
+ end
+
+ describe '#move_project_folders' do
it 'moves the wiki & the repo' do
expect(subject).to receive(:move_repository)
.with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
expect(subject).to receive(:move_repository)
.with(project, 'known-parent/the-path', 'known-parent/the-path0')
- subject.rename_project(project)
+ subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
end
it 'moves uploads' do
expect(subject).to receive(:move_uploads)
.with('known-parent/the-path', 'known-parent/the-path0')
- subject.rename_project(project)
+ subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
end
it 'moves pages' do
expect(subject).to receive(:move_pages)
.with('known-parent/the-path', 'known-parent/the-path0')
- subject.rename_project(project)
+ subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
end
end
@@ -99,4 +114,47 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr
expect(File.directory?(expected_path)).to be(true)
end
end
+
+ describe '#revert_renames', redis: true do
+ it 'renames the routes back to the previous values' do
+ subject.rename_project(project)
+
+ expect(subject).to receive(:perform_rename)
+ .with(
+ kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
+ 'known-parent/the-path0',
+ 'known-parent/the-path'
+ ).and_call_original
+
+ subject.revert_renames
+
+ expect(project.reload.path).to eq('the-path')
+ expect(project.route.path).to eq('known-parent/the-path')
+ end
+
+ it 'moves the repositories back to their original place' do
+ project.create_repository
+ subject.rename_project(project)
+
+ expected_path = File.join(TestEnv.repos_path, 'known-parent', 'the-path.git')
+
+ expect(subject).to receive(:move_project_folders)
+ .with(
+ kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
+ 'known-parent/the-path0',
+ 'known-parent/the-path'
+ ).and_call_original
+
+ subject.revert_renames
+
+ expect(File.directory?(expected_path)).to be_truthy
+ end
+
+ it "doesn't break when the project was renamed" do
+ subject.rename_project(project)
+ project.update_attributes!(path: 'renamed-afterwards')
+
+ expect { subject.revert_renames }.not_to raise_error
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 1d5e58855c1..7695b95dc57 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -51,4 +51,26 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do
subject.rename_root_paths('the-path')
end
end
+
+ describe '#revert_renames' do
+ it 'renames namespaces' do
+ rename_namespaces = double
+ expect(described_class::RenameNamespaces)
+ .to receive(:new).with([], subject)
+ .and_return(rename_namespaces)
+ expect(rename_namespaces).to receive(:revert_renames)
+
+ subject.revert_renames
+ end
+
+ it 'renames projects' do
+ rename_projects = double
+ expect(described_class::RenameProjects)
+ .to receive(:new).with([], subject)
+ .and_return(rename_projects)
+ expect(rename_projects).to receive(:revert_renames)
+
+ subject.revert_renames
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
new file mode 100644
index 00000000000..62c1d37ea1c
--- /dev/null
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Database::ShaAttribute do
+ let(:sha) do
+ '9a573a369a5bfbb9a4a36e98852c21af8a44ea8b'
+ end
+
+ let(:binary_sha) do
+ [sha].pack('H*')
+ end
+
+ let(:binary_from_db) do
+ if Gitlab::Database.postgresql?
+ "\\x#{sha}"
+ else
+ binary_sha
+ end
+ end
+
+ let(:attribute) { described_class.new }
+
+ describe '#type_cast_from_database' do
+ it 'converts the binary SHA to a String' do
+ expect(attribute.type_cast_from_database(binary_from_db)).to eq(sha)
+ end
+ end
+
+ describe '#type_cast_for_database' do
+ it 'converts a SHA String to binary data' do
+ expect(attribute.type_cast_for_database(sha).to_s).to eq(binary_sha)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 7e32770f95d..64b233f3e68 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -87,5 +87,9 @@ describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
it 'links URLs' do
expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl'))
end
+
+ it 'does not contain link with a newline as package name' do
+ expect(subject).not_to include(link("\n", "https://pypi.python.org/pypi/\n"))
+ end
end
end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 8b041ac69b1..66c016d14b3 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(95)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("# Contribute to GitLab")
+ expect(data.first[:line]).to be_utf8
end
end
@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("Ä ü")
+ expect(data.first[:line]).to be_utf8
end
end
@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq(" ")
+ expect(data.first[:line]).to be_utf8
end
end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 58d3ee6b488..3c784eda4f8 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -111,7 +111,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
- describe '.raw' do
+ shared_examples 'finding blobs by ID' do
let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) }
it { expect(raw_blob.data[0..10]).to eq("require \'fi") }
@@ -136,6 +136,16 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
+ describe '.raw' do
+ context 'when the blob_raw Gitaly feature is enabled' do
+ it_behaves_like 'finding blobs by ID'
+ end
+
+ context 'when the blob_raw Gitaly feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding blobs by ID'
+ end
+ end
+
describe 'encoding' do
context 'file with russian text' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") }
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 9dba4397e79..d1d7ed1d02a 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do
expect(Gitlab::Git::Commit).to receive(:decorate)
.with(hash_including(attributes)).and_call_original
- expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8)
+ expect(branch.dereferenced_target.message).to be_utf8
end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index d50ccb0df30..d97e85364c2 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -180,7 +180,7 @@ EOT
let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
it 'encodes diff patch to UTF-8' do
- expect(diff.diff.encoding).to eq(Encoding::UTF_8)
+ expect(diff.diff).to be_utf8
end
end
end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index 3f279c21865..73518656bde 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -4,18 +4,20 @@ require 'fileutils'
describe Gitlab::Git::Hook, lib: true do
describe "#trigger" do
let(:project) { create(:project, :repository) }
+ let(:repo_path) { project.repository.path }
let(:user) { create(:user) }
+ let(:gl_id) { Gitlab::GlId.gl_id(user) }
def create_hook(name)
- FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
- File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+ FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+ File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
f.write('exit 0')
end
end
def create_failing_hook(name)
- FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
- File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+ FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+ File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
f.write(<<-HOOK)
echo 'regular message from the hook'
echo 'error message from the hook' 1>&2
@@ -27,13 +29,29 @@ describe Gitlab::Git::Hook, lib: true do
['pre-receive', 'post-receive', 'update'].each do |hook_name|
context "when triggering a #{hook_name} hook" do
context "when the hook is successful" do
+ let(:hook_path) { File.join(repo_path, 'hooks', hook_name) }
+ let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
+ let(:env) do
+ {
+ 'GL_ID' => gl_id,
+ 'PWD' => repo_path,
+ 'GL_PROTOCOL' => 'web',
+ 'GL_REPOSITORY' => gl_repository
+ }
+ end
+
it "returns success with no errors" do
create_hook(hook_name)
- hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+ hook = Gitlab::Git::Hook.new(hook_name, project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
- status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ if hook_name != 'update'
+ expect(Open3).to receive(:popen3)
+ .with(env, hook_path, chdir: repo_path).and_call_original
+ end
+
+ status, errors = hook.trigger(gl_id, blank, blank, ref)
expect(status).to be true
expect(errors).to be_blank
end
@@ -42,11 +60,11 @@ describe Gitlab::Git::Hook, lib: true do
context "when the hook is unsuccessful" do
it "returns failure with errors" do
create_failing_hook(hook_name)
- hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+ hook = Gitlab::Git::Hook.new(hook_name, project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
- status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ status, errors = hook.trigger(gl_id, blank, blank, ref)
expect(status).to be false
expect(errors).to eq("error message from the hook\n")
end
@@ -56,11 +74,11 @@ describe Gitlab::Git::Hook, lib: true do
context "when the hook doesn't exist" do
it "returns success with no errors" do
- hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path)
+ hook = Gitlab::Git::Hook.new('unknown_hook', project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
- status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ status, errors = hook.trigger(gl_id, blank, blank, ref)
expect(status).to be true
expect(errors).to be_nil
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 4894b558e03..acffd335e43 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -3,6 +3,20 @@ require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
include Gitlab::EncodingHelper
+ shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
+ it 'wraps gRPC not found error' do
+ expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
+ .and_raise(GRPC::NotFound)
+ expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+
+ it 'wraps gRPC unknown error' do
+ expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
+ .and_raise(GRPC::Unknown)
+ expect { subject }.to raise_error(Gitlab::Git::CommandError)
+ end
+ end
+
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
describe "Respond to" do
@@ -26,31 +40,17 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- context 'with gitaly enabled' do
- before do
- stub_gitaly
- end
-
- after do
- Gitlab::GitalyClient.clear_stubs!
- end
-
- it 'gets the branch name from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- repository.root_ref
- end
+ it 'returns UTF-8' do
+ expect(repository.root_ref).to be_utf8
+ end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- .and_raise(GRPC::NotFound)
- expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
+ it 'gets the branch name from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+ repository.root_ref
+ end
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- .and_raise(GRPC::Unknown)
- expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :default_branch_name do
+ subject { repository.root_ref }
end
end
@@ -123,45 +123,35 @@ describe Gitlab::Git::Repository, seed_helper: true do
it 'has SeedRepo::Repo::BRANCHES.size elements' do
expect(subject.size).to eq(SeedRepo::Repo::BRANCHES.size)
end
- it { is_expected.to include("master") }
- it { is_expected.not_to include("branch-from-space") }
- context 'with gitaly enabled' do
- before do
- stub_gitaly
- end
-
- after do
- Gitlab::GitalyClient.clear_stubs!
- end
-
- it 'gets the branch names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- subject
- end
+ it 'returns UTF-8' do
+ expect(subject.first).to be_utf8
+ end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- .and_raise(GRPC::NotFound)
- expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
+ it { is_expected.to include("master") }
+ it { is_expected.not_to include("branch-from-space") }
- it 'wraps GRPC other exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- .and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
+ it 'gets the branch names from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+ subject
end
+
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :branch_names
end
describe '#tag_names' do
subject { repository.tag_names }
it { is_expected.to be_kind_of Array }
+
it 'has SeedRepo::Repo::TAGS.size elements' do
expect(subject.size).to eq(SeedRepo::Repo::TAGS.size)
end
+ it 'returns UTF-8' do
+ expect(subject.first).to be_utf8
+ end
+
describe '#last' do
subject { super().last }
it { is_expected.to eq("v1.2.1") }
@@ -169,32 +159,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to include("v1.0.0") }
it { is_expected.not_to include("v5.0.0") }
- context 'with gitaly enabled' do
- before do
- stub_gitaly
- end
-
- after do
- Gitlab::GitalyClient.clear_stubs!
- end
-
- it 'gets the tag names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- subject
- end
-
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- .and_raise(GRPC::NotFound)
- expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- .and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
+ it 'gets the tag names from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+ subject
end
+
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :tag_names
end
shared_examples 'archive check' do |extenstion|
@@ -344,11 +314,43 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#submodule_url_for' do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+ let(:ref) { 'master' }
+
+ def submodule_url(path)
+ repository.submodule_url_for(ref, path)
+ end
+
+ it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('nested/six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('deeper/nested/six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('invalid/path')).to eq(nil) }
+
+ context 'uncommitted submodule dir' do
+ let(:ref) { 'fix-existing-submodule-dir' }
+
+ it { expect(submodule_url('submodule-existing-dir')).to eq(nil) }
+ end
+
+ context 'tags' do
+ let(:ref) { 'v1.2.1' }
+
+ it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+ end
+
+ context 'no submodules at commit' do
+ let(:ref) { '6d39438' }
+
+ it { expect(submodule_url('six')).to eq(nil) }
+ end
+ end
+
context '#submodules' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
context 'where repo has submodules' do
- let(:submodules) { repository.submodules('master') }
+ let(:submodules) { repository.send(:submodules, 'master') }
let(:submodule) { submodules.first }
it { expect(submodules).to be_kind_of Hash }
@@ -383,12 +385,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it 'should not have an entry for an uncommited submodule dir' do
- submodules = repository.submodules('fix-existing-submodule-dir')
+ submodules = repository.send(:submodules, 'fix-existing-submodule-dir')
expect(submodules).not_to have_key('submodule-existing-dir')
end
it 'should handle tags correctly' do
- submodules = repository.submodules('v1.2.1')
+ submodules = repository.send(:submodules, 'v1.2.1')
expect(submodules.first).to eq([
"six", {
@@ -414,7 +416,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
context 'where repo doesn\'t have submodules' do
- let(:submodules) { repository.submodules('6d39438') }
+ let(:submodules) { repository.send(:submodules, '6d39438') }
it 'should return an empty hash' do
expect(submodules).to be_empty
end
@@ -422,8 +424,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#commit_count' do
- it { expect(repository.commit_count("master")).to eq(25) }
- it { expect(repository.commit_count("feature")).to eq(9) }
+ shared_examples 'counting commits' do
+ it { expect(repository.commit_count("master")).to eq(25) }
+ it { expect(repository.commit_count("feature")).to eq(9) }
+ end
+
+ context 'when Gitaly commit_count feature is enabled' do
+ it_behaves_like 'counting commits'
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Commit, :commit_count do
+ subject { repository.commit_count('master') }
+ end
+ end
+
+ context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'counting commits'
+ end
end
describe "#reset" do
@@ -1267,32 +1282,23 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
- context 'with gitaly enabled' do
- before do
- stub_gitaly
- end
-
- after do
- Gitlab::GitalyClient.clear_stubs!
- end
-
- it 'gets the branches from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
- .and_return([])
- @repo.local_branches
+ it 'returns a Branch with UTF-8 fields' do
+ branches = @repo.local_branches.to_a
+ expect(branches.size).to be > 0
+ branches.each do |branch|
+ expect(branch.name).to be_utf8
+ expect(branch.target).to be_utf8 unless branch.target.nil?
end
+ end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
- .and_raise(GRPC::NotFound)
- expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
+ it 'gets the branches from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
+ .and_return([])
+ @repo.local_branches
+ end
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
- .and_raise(GRPC::Unknown)
- expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :local_branches do
+ subject { @repo.local_branches }
end
end
@@ -1371,11 +1377,4 @@ describe Gitlab::Git::Repository, seed_helper: true do
sha = Rugged::Commit.create(repo, options)
repo.lookup(sha)
end
-
- def stub_gitaly
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
-
- stub = double(:stub)
- allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub)
- end
end
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 42dba2ff874..7c090460764 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do
let(:relative_path) { project.path_with_namespace + '.git' }
let(:client) { described_class.new(project.repository) }
- before do
- allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
- end
-
- after do
- # When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
- # and because GitalyClient shares stubs these will get passed from example to
- # example, which will cause an error, so we clean the stubs after each example.
- Gitlab::GitalyClient.clear_stubs!
- end
-
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub)
@@ -69,8 +58,26 @@ describe Gitlab::GitalyClient::Ref do
client.local_branches(sort_by: 'updated_desc')
end
+ it 'translates known mismatches on sort param values' do
+ expect_any_instance_of(Gitaly::Ref::Stub)
+ .to receive(:find_local_branches)
+ .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash))
+ .and_return([])
+
+ client.local_branches(sort_by: 'name_asc')
+ end
+
it 'raises an argument error if an invalid sort_by parameter is passed' do
expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
end
end
+
+ describe '#find_ref_name', seed_helper: true do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+ let(:client) { described_class.new(repository) }
+ subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
+
+ it { is_expected.to be_utf8 }
+ it { is_expected.to eq('refs/heads/master') }
+ end
end
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index 61c10d47434..b333e162909 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
- it { is_expected.to all(have_attributes(labels: { shard: :default })) }
+ # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
+ around(:each) do |example| # rubocop:disable RSpec/AroundBlock
+ times_to_try = ENV['CI'] ? 4 : 1
+ example.run_with_retry retry: times_to_try
+ end
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) }
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: :default }))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+ end
end
context 'storage points to directory that has both read and write rights' do
before do
FileUtils.chmod_R(0755, tmp_dir)
end
- it { is_expected.to all(have_attributes(labels: { shard: :default })) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: :default }))
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
- it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
+
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a5f09f1856e..977174a5fd2 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -45,6 +45,7 @@ label:
- merge_requests
- priorities
milestone:
+- group
- project
- issues
- labels
@@ -88,7 +89,10 @@ merge_requests:
- head_pipeline
merge_request_diff:
- merge_request
+- merge_request_diff_commits
- merge_request_diff_files
+merge_request_diff_commits:
+- merge_request_diff
merge_request_diff_files:
- merge_request_diff
pipelines:
@@ -130,8 +134,11 @@ pipeline_schedules:
- owner
- pipelines
- last_pipeline
+- variables
pipeline_schedule:
- pipelines
+pipeline_schedule_variables:
+- pipeline_schedule
deploy_keys:
- user
- deploy_keys_projects
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 98c117b4cd8..469a014e4d2 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2741,13 +2741,12 @@
"merge_request_diff": {
"id": 27,
"state": "collected",
- "st_commits": [
+ "merge_request_diff_commits": [
{
- "id": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
+ "merge_request_diff_id": 27,
+ "relative_order": 0,
+ "sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
"message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "5937ac0a7beb003549fc5fd26fc247adbce4a52e"
- ],
"authored_date": "2014-08-06T08:35:52.000+02:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2756,11 +2755,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
+ "merge_request_diff_id": 27,
+ "relative_order": 1,
+ "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
- ],
"authored_date": "2014-02-27T10:01:38.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2769,11 +2767,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
+ "merge_request_diff_id": 27,
+ "relative_order": 2,
+ "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- ],
"authored_date": "2014-02-27T09:57:31.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2782,11 +2779,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
+ "merge_request_diff_id": 27,
+ "relative_order": 3,
+ "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
"message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "d14d6c0abdd253381df51a723d58691b2ee1ab08"
- ],
"authored_date": "2014-02-27T09:54:21.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2795,11 +2791,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
+ "merge_request_diff_id": 27,
+ "relative_order": 4,
+ "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
"message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "c1acaa58bbcbc3eafe538cb8274ba387047b69f8"
- ],
"authored_date": "2014-02-27T09:49:50.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2808,11 +2803,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
+ "merge_request_diff_id": 27,
+ "relative_order": 5,
+ "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
"message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "ae73cb07c9eeaf35924a10f713b364d32b2dd34f"
- ],
"authored_date": "2014-02-27T09:48:32.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index c11b15a811b..d50d238ddcd 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -95,6 +95,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequestDiffFile.where.not(diff: nil).count).to eq(9)
end
+ it 'has the correct data for merge request diff commits in serialised and table formats' do
+ expect(MergeRequestDiff.where.not(st_commits: nil).count).to eq(7)
+ expect(MergeRequestDiffCommit.count).to eq(6)
+ end
+
it 'has the correct time for merge request st_commits' do
st_commits = MergeRequestDiff.where.not(st_commits: nil).first.st_commits
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index e52f79513f1..22a65e24f26 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -87,6 +87,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_files']).not_to be_empty
end
+ it 'has merge request diff commits' do
+ expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_commits']).not_to be_empty
+ end
+
it 'has merge requests comments' do
expect(saved_project_json['merge_requests'].first['notes']).not_to be_empty
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 168a59e5139..30b6a0d8845 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::ImportExport::RepoRestorer, services: true do
it 'has the webhooks' do
restorer.restore
- expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist
+ expect(Gitlab::Git::Hook.new('post-receive', project)).to exist
end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index fadd3ad1330..4ef3db3721f 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -82,6 +82,7 @@ Milestone:
- id
- title
- project_id
+- group_id
- description
- due_date
- start_date
@@ -172,6 +173,17 @@ MergeRequestDiff:
- real_size
- head_commit_sha
- start_commit_sha
+MergeRequestDiffCommit:
+- merge_request_diff_id
+- relative_order
+- sha
+- authored_date
+- committed_date
+- author_name
+- author_email
+- committer_name
+- committer_email
+- message
MergeRequestDiffFile:
- merge_request_diff_id
- relative_order
@@ -383,6 +395,7 @@ Project:
- printing_merge_request_link_enabled
- build_allow_git_fetch
- last_repository_updated_at
+- ci_config_path
Author:
- name
ProjectFeature:
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
new file mode 100644
index 00000000000..f9f4b290dbf
--- /dev/null
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Gitlab::IssuableMetadata, lib: true do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
+
+ subject { Class.new { include Gitlab::IssuableMetadata }.new }
+
+ it 'returns an empty Hash if an empty collection is provided' do
+ expect(subject.issuable_meta_data(Issue.none, 'Issue')).to eq({})
+ end
+
+ context 'issues' do
+ let!(:issue) { create(:issue, author: user, project: project) }
+ let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) }
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) }
+
+ it 'aggregates stats on issues' do
+ data = subject.issuable_meta_data(Issue.all, 'Issue')
+
+ expect(data.count).to eq(2)
+ expect(data[issue.id].upvotes).to eq(1)
+ expect(data[issue.id].downvotes).to eq(0)
+ expect(data[issue.id].notes_count).to eq(0)
+ expect(data[issue.id].merge_requests_count).to eq(1)
+
+ expect(data[closed_issue.id].upvotes).to eq(0)
+ expect(data[closed_issue.id].downvotes).to eq(1)
+ expect(data[closed_issue.id].notes_count).to eq(0)
+ expect(data[closed_issue.id].merge_requests_count).to eq(0)
+ end
+ end
+
+ context 'merge requests' do
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
+ let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+
+ it 'aggregates stats on merge requests' do
+ data = subject.issuable_meta_data(MergeRequest.all, 'MergeRequest')
+
+ expect(data.count).to eq(2)
+ expect(data[merge_request.id].upvotes).to eq(1)
+ expect(data[merge_request.id].downvotes).to eq(1)
+ expect(data[merge_request.id].notes_count).to eq(1)
+ expect(data[merge_request.id].merge_requests_count).to eq(0)
+
+ expect(data[merge_request_closed.id].upvotes).to eq(0)
+ expect(data[merge_request_closed.id].downvotes).to eq(0)
+ expect(data[merge_request_closed.id].notes_count).to eq(0)
+ expect(data[merge_request_closed.id].merge_requests_count).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index e8c599a95ee..34b33772578 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -46,4 +46,28 @@ describe Gitlab::Kubernetes do
expect(filter_by_label(items, app: 'foo')).to eq(matching_items)
end
end
+
+ describe '#to_kubeconfig' do
+ subject do
+ to_kubeconfig(
+ url: 'https://kube.domain.com',
+ namespace: 'NAMESPACE',
+ token: 'TOKEN',
+ ca_pem: ca_pem)
+ end
+
+ context 'when CA PEM is provided' do
+ let(:ca_pem) { 'PEM' }
+ let(:path) { expand_fixture_path('config/kubeconfig.yml') }
+
+ it { is_expected.to eq(YAML.load_file(path)) }
+ end
+
+ context 'when CA PEM is not provided' do
+ let(:ca_pem) { nil }
+ let(:path) { expand_fixture_path('config/kubeconfig-without-ca.yml') }
+
+ it { is_expected.to eq(YAML.load_file(path)) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 9dd997aa7dc..756fcb0fcaf 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -4,6 +4,16 @@ describe Gitlab::LDAP::Access, lib: true do
let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:omniauth_user) }
+ describe '.allowed?' do
+ it 'updates the users `last_credential_check_at' do
+ expect(access).to receive(:allowed?) { true }
+ expect(described_class).to receive(:open).and_yield(access)
+
+ expect { described_class.allowed?(user) }
+ .to change { user.last_credential_check_at }
+ end
+ end
+
describe '#allowed?' do
subject { access.allowed? }
diff --git a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb
new file mode 100644
index 00000000000..94251af305f
--- /dev/null
+++ b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::ConnectionRackMiddleware do
+ let(:app) { double('app') }
+ subject { described_class.new(app) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ describe '#call' do
+ let(:status) { 100 }
+ let(:env) { { 'REQUEST_METHOD' => 'GET' } }
+ let(:stack_result) { [status, {}, 'body'] }
+
+ before do
+ allow(app).to receive(:call).and_return(stack_result)
+ end
+
+ context '@app.call succeeds with 200' do
+ before do
+ allow(app).to receive(:call).and_return([200, nil, nil])
+ end
+
+ it 'increments response count with status label' do
+ expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get'))
+
+ subject.call(env)
+ end
+
+ it 'increments requests count' do
+ expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
+
+ subject.call(env)
+ end
+
+ it 'measures execution time' do
+ execution_time = 10
+ allow(app).to receive(:call) do |*args|
+ Timecop.freeze(execution_time.seconds)
+ end
+
+ expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
+
+ subject.call(env)
+ end
+ end
+
+ context '@app.call throws exception' do
+ let(:rack_response_count) { double('rack_response_count') }
+
+ before do
+ allow(app).to receive(:call).and_raise(StandardError)
+ allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
+ end
+
+ it 'increments exceptions count' do
+ expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+
+ it 'increments requests count' do
+ expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+
+ it "does't increment response count" do
+ expect(described_class.rack_response_count).not_to receive(:increment)
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+
+ it 'measures execution time' do
+ execution_time = 10
+ allow(app).to receive(:call) do |*args|
+ Timecop.freeze(execution_time.seconds)
+ raise StandardError
+ end
+
+ expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
index d07ce6f81af..0bc68d64276 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Metrics::Sampler do
+describe Gitlab::Metrics::InfluxSampler do
let(:sampler) { described_class.new(5) }
after do
@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
end
describe '#start' do
- it 'gathers a sample at a given interval' do
- expect(sampler).to receive(:sleep).with(a_kind_of(Numeric))
- expect(sampler).to receive(:sample)
- expect(sampler).to receive(:loop).and_yield
+ it 'runs once and gathers a sample at a given interval' do
+ expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
+ expect(sampler).to receive(:sample).once
+ expect(sampler).to receive(:running).and_return(false, true, false)
sampler.start.join
end
diff --git a/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
new file mode 100644
index 00000000000..dc0d1f2e940
--- /dev/null
+++ b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
@@ -0,0 +1,108 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::UnicornSampler do
+ subject { described_class.new(1.second) }
+
+ describe '#sample' do
+ let(:unicorn) { double('unicorn') }
+ let(:raindrops) { double('raindrops') }
+ let(:stats) { double('stats') }
+
+ before do
+ stub_const('Unicorn', unicorn)
+ stub_const('Raindrops::Linux', raindrops)
+ allow(raindrops).to receive(:unix_listener_stats).and_return({})
+ allow(raindrops).to receive(:tcp_listener_stats).and_return({})
+ end
+
+ context 'unicorn listens on unix sockets' do
+ let(:socket_address) { '/some/sock' }
+ let(:sockets) { [socket_address] }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return(sockets)
+ end
+
+ it 'samples socket data' do
+ expect(raindrops).to receive(:unix_listener_stats).with(sockets)
+
+ subject.sample
+ end
+
+ context 'stats collected' do
+ before do
+ allow(stats).to receive(:active).and_return('active')
+ allow(stats).to receive(:queued).and_return('queued')
+ allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
+ end
+
+ it 'updates metrics type unix and with addr' do
+ labels = { type: 'unix', address: socket_address }
+
+ expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+ expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+ subject.sample
+ end
+ end
+ end
+
+ context 'unicorn listens on tcp sockets' do
+ let(:tcp_socket_address) { '0.0.0.0:8080' }
+ let(:tcp_sockets) { [tcp_socket_address] }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
+ end
+
+ it 'samples socket data' do
+ expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
+
+ subject.sample
+ end
+
+ context 'stats collected' do
+ before do
+ allow(stats).to receive(:active).and_return('active')
+ allow(stats).to receive(:queued).and_return('queued')
+ allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
+ end
+
+ it 'updates metrics type unix and with addr' do
+ labels = { type: 'tcp', address: tcp_socket_address }
+
+ expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+ expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+ subject.sample
+ end
+ end
+ end
+ end
+
+ describe '#start' do
+ context 'when enabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(true)
+ end
+
+ it 'creates new thread' do
+ expect(Thread).to receive(:new)
+
+ subject.start
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(false)
+ end
+
+ it "doesn't create new thread" do
+ expect(Thread).not_to receive(:new)
+
+ subject.start
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
new file mode 100644
index 00000000000..8a586bdbf63
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe Gitlab::PerformanceBar do
+ shared_examples 'allowed user IDs are cached' do
+ before do
+ # Warm the Redis cache
+ described_class.enabled?(user)
+ end
+
+ it 'caches the allowed user IDs in cache', :caching do
+ expect do
+ expect(described_class.enabled?(user)).to be_truthy
+ end.not_to exceed_query_limit(0)
+ end
+ end
+
+ describe '.enabled?' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_application_setting(performance_bar_allowed_group_id: -1)
+ end
+
+ it 'returns false when given user is nil' do
+ expect(described_class.enabled?(nil)).to be_falsy
+ end
+
+ it 'returns false when allowed_group_id is nil' do
+ expect(described_class).to receive(:allowed_group_id).and_return(nil)
+
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+
+ context 'when allowed group ID does not exist' do
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+ end
+
+ context 'when allowed group exists' do
+ let!(:my_group) { create(:group, path: 'my-group') }
+
+ before do
+ stub_application_setting(performance_bar_allowed_group_id: my_group.id)
+ end
+
+ context 'when user is not a member of the allowed group' do
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+
+ it_behaves_like 'allowed user IDs are cached'
+ end
+
+ context 'when user is a member of the allowed group' do
+ before do
+ my_group.add_developer(user)
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?(user)).to be_truthy
+ end
+
+ it_behaves_like 'allowed user IDs are cached'
+ end
+ end
+
+ context 'when allowed group is nested', :nested_groups do
+ let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
+
+ before do
+ create(:group, path: 'my-group')
+ nested_my_group.add_developer(user)
+ stub_application_setting(performance_bar_allowed_group_id: nested_my_group.id)
+ end
+
+ it 'returns the nested group' do
+ expect(described_class.enabled?(user)).to be_truthy
+ end
+ end
+
+ context 'when a nested group has the same path', :nested_groups do
+ before do
+ create(:group, :nested, path: 'my-group').add_developer(user)
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 4ae216d55b0..af50ecdb2ab 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -32,6 +32,17 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
end
end
+ context 'with custom options' do
+ let(:vars) { { 'foobar' => 123, 'PWD' => path } }
+ let(:options) { { chdir: path } }
+
+ it 'calls popen3 with the provided environment variables' do
+ expect(Open3).to receive(:popen3).with(vars, 'ls', options)
+
+ @output, @status = @klass.new.popen(%w(ls), path, { 'foobar' => 123 })
+ end
+ end
+
context 'without a directory argument' do
before do
@output, @status = @klass.new.popen(%w(ls))
@@ -45,7 +56,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
before do
@output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' }
end
-
+
it { expect(@status).to be_zero }
it { expect(@output).to eq('hello') }
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 979f4fefcb6..51e2c3c38c6 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,12 +14,6 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('?gitlab') }
end
- describe '.file_name_regex' do
- subject { described_class.file_name_regex }
-
- it { is_expected.to match('foo@bar') }
- end
-
describe '.environment_slug_regex' do
subject { described_class.environment_name_regex }
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index a97a0f8452b..5b1b8f9516a 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,6 +4,7 @@ require 'stringio'
describe Gitlab::Shell, lib: true do
let(:project) { double('Project', id: 7, path: 'diaspora') }
let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
before do
allow(Project).to receive(:find).and_return(project)
@@ -50,7 +51,7 @@ describe Gitlab::Shell, lib: true do
describe '#add_key' do
it 'removes trailing garbage' do
allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(Gitlab::Utils).to receive(:system_silent).with(
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
[:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
)
@@ -100,17 +101,91 @@ describe Gitlab::Shell, lib: true do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
+ describe '#add_repository' do
+ it 'returns true when the command succeeds' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
+ nil, popen_vars).and_return([nil, 0])
+
+ expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be true
+ end
+
+ it 'returns false when the command fails' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
+ nil, popen_vars).and_return(["error", 1])
+
+ expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be false
+ end
+ end
+
+ describe '#remove_repository' do
+ it 'returns true when the command succeeds' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
+ nil, popen_vars).and_return([nil, 0])
+
+ expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true
+ end
+
+ it 'returns false when the command fails' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
+ nil, popen_vars).and_return(["error", 1])
+
+ expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false
+ end
+ end
+
+ describe '#mv_repository' do
+ it 'returns true when the command succeeds' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
+ nil, popen_vars).and_return([nil, 0])
+
+ expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be true
+ end
+
+ it 'returns false when the command fails' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
+ nil, popen_vars).and_return(["error", 1])
+
+ expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be false
+ end
+ end
+
+ describe '#fork_repository' do
+ it 'returns true when the command succeeds' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'],
+ nil, popen_vars).and_return([nil, 0])
+
+ expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be true
+ end
+
+ it 'return false when the command fails' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'],
+ nil, popen_vars).and_return(["error", 1])
+
+ expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be false
+ end
+ end
+
describe '#fetch_remote' do
it 'returns true when the command succeeds' do
expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return([nil, 0])
+ .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
+ nil, popen_vars).and_return([nil, 0])
expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true
end
it 'raises an exception when the command fails' do
expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return(["error", 1])
+ .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
+ nil, popen_vars).and_return(["error", 1])
expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error")
end
@@ -119,14 +194,16 @@ describe Gitlab::Shell, lib: true do
describe '#import_repository' do
it 'returns true when the command succeeds' do
expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return([nil, 0])
+ .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
+ nil, popen_vars).and_return([nil, 0])
expect(gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git')).to be true
end
it 'raises an exception when the command fails' do
expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return(["error", 1])
+ .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
+ nil, popen_vars).and_return(["error", 1])
expect { gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git') }.to raise_error(Gitlab::Shell::Error, "error")
end
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
new file mode 100644
index 00000000000..451c583310d
--- /dev/null
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::Glob, lib: true do
+ describe '.to_like' do
+ it 'matches * as %' do
+ expect(glob('apple', '*')).to be(true)
+ expect(glob('apple', 'app*')).to be(true)
+ expect(glob('apple', 'apple*')).to be(true)
+ expect(glob('apple', '*pple')).to be(true)
+ expect(glob('apple', 'ap*le')).to be(true)
+
+ expect(glob('apple', '*a')).to be(false)
+ expect(glob('apple', 'app*a')).to be(false)
+ expect(glob('apple', 'ap*l')).to be(false)
+ end
+
+ it 'matches % literally' do
+ expect(glob('100%', '100%')).to be(true)
+
+ expect(glob('100%', '%')).to be(false)
+ end
+
+ it 'matches _ literally' do
+ expect(glob('^_^', '^_^')).to be(true)
+
+ expect(glob('^A^', '^_^')).to be(false)
+ end
+ end
+
+ def glob(string, pattern)
+ match(string, subject.to_like(quote(pattern)))
+ end
+
+ def match(string, pattern)
+ value = query("SELECT #{quote(string)} LIKE #{pattern}")
+ .rows.flatten.first
+
+ case value
+ when 't', 1
+ true
+ else
+ false
+ end
+ end
+
+ def query(sql)
+ ActiveRecord::Base.connection.select_all(sql)
+ end
+
+ def quote(string)
+ ActiveRecord::Base.connection.quote(string)
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 3c7c7562b46..c6718827028 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -30,7 +30,8 @@ describe Gitlab::UsageData do
expect(count_data.keys).to match_array(%i(
boards
ci_builds
- ci_pipelines
+ ci_internal_pipelines
+ ci_external_pipelines
ci_runners
ci_triggers
ci_pipeline_schedules
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 980b24370d0..683e893968b 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -52,7 +52,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(issue)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
@@ -99,7 +99,7 @@ describe Notify do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_html_escaped_body_text(previous_assignee.name)
is_expected.to have_html_escaped_body_text(assignee.name)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
end
@@ -125,7 +125,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text('foo, bar, and baz')
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
@@ -165,7 +165,7 @@ describe Notify do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(status)
is_expected.to have_html_escaped_body_text(current_user.name)
- is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
+ is_expected.to have_body_text(project_issue_path project, issue)
end
end
end
@@ -185,13 +185,12 @@ describe Notify do
end
it 'has the correct subject and body' do
- new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
- new_issue.project, new_issue)
+ new_issue_url = project_issue_path(new_issue.project, new_issue)
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(new_issue_url)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
end
@@ -216,7 +215,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(merge_request)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text(merge_request.source_branch)
is_expected.to have_body_text(merge_request.target_branch)
end
@@ -265,7 +264,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_html_escaped_body_text(previous_assignee.name)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_html_escaped_body_text(assignee.name)
end
end
@@ -291,7 +290,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('foo, bar, and baz')
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
@@ -316,7 +315,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(status)
is_expected.to have_html_escaped_body_text(current_user.name)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
end
@@ -341,7 +340,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('merged')
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
end
@@ -390,7 +389,7 @@ describe Notify do
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace
- is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
end
@@ -417,7 +416,7 @@ describe Notify do
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace
- is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
end
@@ -609,7 +608,7 @@ describe Notify do
describe 'on a merge request' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+ let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(merge_request)
@@ -634,7 +633,7 @@ describe Notify do
describe 'on an issue' do
let(:issue) { create(:issue, project: project) }
- let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+ let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(issue)
@@ -725,7 +724,7 @@ describe Notify do
describe 'on a merge request' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) }
- let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+ let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(merge_request)
@@ -752,7 +751,7 @@ describe Notify do
describe 'on an issue' do
let(:issue) { create(:issue, project: project) }
let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) }
- let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+ let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(issue)
@@ -1022,7 +1021,7 @@ describe Notify do
describe 'email on push for a created branch' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") }
+ let(:tree_path) { project_tree_path(project, "empty-branch") }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
@@ -1048,7 +1047,7 @@ describe Notify do
describe 'email on push for a created tag' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
+ let(:tree_path) { project_tree_path(project, "v1.0") }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
@@ -1122,7 +1121,7 @@ describe Notify do
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
- let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
+ let(:diff_path) { project_compare_path(project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
let(:send_from_committer_email) { false }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
@@ -1216,7 +1215,7 @@ describe Notify do
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
- let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
+ let(:diff_path) { project_commit_path(project, commits.first) }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
new file mode 100644
index 00000000000..260378adaa7
--- /dev/null
+++ b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background')
+
+describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do
+ matcher :be_scheduled_migration do |delay, *expected|
+ match do |migration|
+ BackgroundMigrationWorker.jobs.any? do |job|
+ job['args'] == [migration, expected] &&
+ job['at'].to_i == (delay.to_i + Time.now.to_i)
+ end
+ end
+
+ failure_message do |migration|
+ "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
+ end
+ end
+
+ let(:jobs) { table(:ci_builds) }
+ let(:stages) { table(:ci_stages) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:projects) { table(:projects) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 3)
+ stub_const("#{described_class.name}::RANGE_SIZE", 2)
+
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 345, name: 'gitlab2', path: 'gitlab2')
+
+ pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
+ pipelines.create!(id: 2, project_id: 345, ref: 'feature', sha: 'cdf43c3c')
+
+ jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
+ jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 5, commit_id: 2, project_id: 345, stage_idx: 1, stage: 'test')
+
+ stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test')
+ stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build')
+ stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy')
+
+ jobs.create!(id: 6, commit_id: 2, project_id: 345, stage_id: 101, stage_idx: 1, stage: 'test')
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(2.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(2.minutes, 3, 3)
+ expect(described_class::MIGRATION).to be_scheduled_migration(4.minutes, 4, 5)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.inline! do
+ expect(jobs.where(stage_id: nil).count).to eq 5
+
+ migrate!
+
+ expect(jobs.where(stage_id: nil).count).to eq 1
+ end
+ end
+end
diff --git a/spec/migrations/rename_duplicated_variable_key_spec.rb b/spec/migrations/rename_duplicated_variable_key_spec.rb
new file mode 100644
index 00000000000..11096564dfa
--- /dev/null
+++ b/spec/migrations/rename_duplicated_variable_key_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170622135451_rename_duplicated_variable_key.rb')
+
+describe RenameDuplicatedVariableKey, :migration do
+ let(:variables) { table(:ci_variables) }
+ let(:projects) { table(:projects) }
+
+ before do
+ projects.create!(id: 1)
+ variables.create!(id: 1, key: 'key1', project_id: 1)
+ variables.create!(id: 2, key: 'key2', project_id: 1)
+ variables.create!(id: 3, key: 'keyX', project_id: 1)
+ variables.create!(id: 4, key: 'keyX', project_id: 1)
+ variables.create!(id: 5, key: 'keyY', project_id: 1)
+ variables.create!(id: 6, key: 'keyX', project_id: 1)
+ variables.create!(id: 7, key: 'key7', project_id: 1)
+ variables.create!(id: 8, key: 'keyY', project_id: 1)
+ end
+
+ it 'correctly remove duplicated records with smaller id' do
+ migrate!
+
+ expect(variables.pluck(:id, :key)).to contain_exactly(
+ [1, 'key1'],
+ [2, 'key2'],
+ [3, 'keyX_3'],
+ [4, 'keyX_4'],
+ [5, 'keyY_5'],
+ [6, 'keyX'],
+ [7, 'key7'],
+ [8, 'keyY']
+ )
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 090f9e70c50..dc7a0d80752 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Ability, lib: true do
context 'using a nil subject' do
- it 'is always empty' do
- expect(Ability.allowed(nil, nil).to_set).to be_empty
+ it 'has no permissions' do
+ expect(Ability.policy_for(nil, nil)).to be_banned
end
end
@@ -255,12 +255,15 @@ describe Ability, lib: true do
describe '.project_disabled_features_rules' do
let(:project) { create(:empty_project, :wiki_disabled) }
- subject { described_class.allowed(project.owner, project) }
+ subject { described_class.policy_for(project.owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
expect(project).to receive(:has_external_wiki?).and_return(false)
- expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
+ expect(subject).not_to be_allowed(:read_wiki)
+ expect(subject).not_to be_allowed(:create_wiki)
+ expect(subject).not_to be_allowed(:update_wiki)
+ expect(subject).not_to be_allowed(:admin_wiki)
end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 166a4474abf..e600eab6565 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -155,6 +155,18 @@ describe ApplicationSetting, models: true do
end
end
+ describe '.current' do
+ context 'redis unavailable' do
+ it 'returns an ApplicationSetting' do
+ allow(Rails.cache).to receive(:fetch).and_call_original
+ allow(ApplicationSetting).to receive(:last).and_return(:last)
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(ArgumentError)
+
+ expect(ApplicationSetting.current).to eq(:last)
+ end
+ end
+ end
+
context 'restricted signup domains' do
it 'sets single domain' do
setting.domain_whitelist_raw = 'example.com'
@@ -214,6 +226,160 @@ describe ApplicationSetting, models: true do
end
end
+ describe 'performance bar settings' do
+ describe 'performance_bar_allowed_group_id=' do
+ context 'with a blank path' do
+ before do
+ setting.performance_bar_allowed_group_id = create(:group).full_path
+ end
+
+ it 'persists nil for a "" path and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = ''
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ end
+ end
+
+ context 'with an invalid path' do
+ it 'does not persist an invalid group path' do
+ setting.performance_bar_allowed_group_id = 'foo'
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ end
+ end
+
+ context 'with a path to an existing group' do
+ let(:group) { create(:group) }
+
+ it 'persists a valid group path and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = group.full_path
+
+ expect(setting.performance_bar_allowed_group_id).to eq(group.id)
+ end
+
+ context 'when the given path is the same' do
+ context 'with a blank path' do
+ before do
+ setting.performance_bar_allowed_group_id = nil
+ end
+
+ it 'clears the cached allowed user IDs' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = ''
+ end
+ end
+
+ context 'with a valid path' do
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'clears the cached allowed user IDs' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+ end
+ end
+ end
+ end
+
+ describe 'performance_bar_allowed_group' do
+ context 'with no performance_bar_allowed_group_id saved' do
+ it 'returns nil' do
+ expect(setting.performance_bar_allowed_group).to be_nil
+ end
+ end
+
+ context 'with a performance_bar_allowed_group_id saved' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'returns the group' do
+ expect(setting.performance_bar_allowed_group).to eq(group)
+ end
+ end
+ end
+
+ describe 'performance_bar_enabled' do
+ context 'with the Performance Bar is enabled' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'returns true' do
+ expect(setting.performance_bar_enabled).to be_truthy
+ end
+ end
+ end
+
+ describe 'performance_bar_enabled=' do
+ context 'when the performance bar is enabled' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ context 'when passing true' do
+ it 'does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = true
+
+ expect(setting.performance_bar_allowed_group_id).to eq(group.id)
+ expect(setting.performance_bar_enabled).to be_truthy
+ end
+ end
+
+ context 'when passing false' do
+ it 'disables the performance bar and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = false
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+ end
+
+ context 'when the performance bar is disabled' do
+ context 'when passing true' do
+ it 'does nothing and does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = true
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+
+ context 'when passing false' do
+ it 'does nothing and does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = false
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
describe 'usage ping settings' do
context 'when the usage ping is disabled in gitlab.yml' do
before do
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
new file mode 100644
index 00000000000..02679dbb544
--- /dev/null
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe BlobViewer::Readme, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'README.md') }
+ subject { described_class.new(blob) }
+
+ describe '#render_error' do
+ context 'when there is no wiki' do
+ it 'returns :no_wiki' do
+ expect(subject.render_error).to eq(:no_wiki)
+ end
+ end
+
+ context 'when there is an external wiki' do
+ before do
+ project.has_external_wiki = true
+ end
+
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+
+ context 'when there is a local wiki' do
+ before do
+ project.wiki_enabled = true
+ end
+
+ context 'when the wiki is empty' do
+ it 'returns :no_wiki' do
+ expect(subject.render_error).to eq(:no_wiki)
+ end
+ end
+
+ context 'when the wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, project.owner, title: 'home', content: 'Home page').execute
+ end
+
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 488697f74eb..154b6759f46 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -863,7 +863,7 @@ describe Ci::Build, :models do
pipeline2 = create(:ci_pipeline, project: project)
@build2 = create(:ci_build, pipeline: pipeline2)
- allow(@merge_request).to receive(:commits_sha)
+ allow(@merge_request).to receive(:commit_shas)
.and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
@@ -998,13 +998,17 @@ describe Ci::Build, :models do
describe '#ref_slug' do
{
- 'master' => 'master',
- '1-foo' => '1-foo',
- 'fix/1-foo' => 'fix-1-foo',
- 'fix-1-foo' => 'fix-1-foo',
- 'a' * 63 => 'a' * 63,
- 'a' * 64 => 'a' * 63,
- 'FOO' => 'foo'
+ 'master' => 'master',
+ '1-foo' => '1-foo',
+ 'fix/1-foo' => 'fix-1-foo',
+ 'fix-1-foo' => 'fix-1-foo',
+ 'a' * 63 => 'a' * 63,
+ 'a' * 64 => 'a' * 63,
+ 'FOO' => 'foo',
+ '-' + 'a' * 61 + '-' => 'a' * 61,
+ '-' + 'a' * 62 + '-' => 'a' * 62,
+ '-' + 'a' * 63 + '-' => 'a' * 62,
+ 'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
@@ -1179,6 +1183,7 @@ describe Ci::Build, :models do
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, 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 },
{ key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
{ key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
@@ -1351,6 +1356,59 @@ describe Ci::Build, :models do
end
end
+ context 'when group secret variable is defined' do
+ let(:secret_variable) do
+ { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ end
+
+ let(:group) { create(:group, :access_requestable) }
+
+ before do
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ secret_variable.slice(:key, :value).merge(group: group))
+ end
+
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when group protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ let(:group) { create(:group, :access_requestable) }
+
+ before do
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(group: group))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
+ end
+
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
@@ -1369,6 +1427,23 @@ describe Ci::Build, :models do
it { is_expected.to include(predefined_trigger_variable) }
end
+ context 'when a job was triggered by a pipeline schedule' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'SCHEDULE_VARIABLE_KEY',
+ pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ pipeline_schedule.pipelines << pipeline
+ pipeline_schedule.reload
+ end
+
+ it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) }
+ end
+
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
@@ -1469,6 +1544,16 @@ describe Ci::Build, :models do
it { is_expected.to include(deployment_variable) }
end
+ context 'when project has custom CI config path' do
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+
+ before do
+ project.update(ci_config_path: 'custom')
+ end
+
+ it { is_expected.to include(ci_config_path) }
+ end
+
context 'returns variables in valid order' do
let(:build_pre_var) { { key: 'build', value: 'value' } }
let(:project_pre_var) { { key: 'project', value: 'value' } }
@@ -1481,9 +1566,10 @@ describe Ci::Build, :models do
allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
- allow(project).to receive(:secret_variables_for).with(build.ref) do
- [create(:ci_variable, key: 'secret', value: 'value')]
- end
+ allow(project).to receive(:secret_variables_for)
+ .with(ref: 'master', environment: nil) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
end
it do
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
new file mode 100644
index 00000000000..24b914face9
--- /dev/null
+++ b/spec/models/ci/group_variable_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Ci::GroupVariable, models: true do
+ subject { build(:ci_group_variable) }
+
+ it { is_expected.to include_module(HasVariable) }
+ it { is_expected.to include_module(Presentable) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id) }
+
+ describe '.unprotected' do
+ subject { described_class.unprotected }
+
+ context 'when variable is protected' do
+ before do
+ create(:ci_group_variable, :protected)
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when variable is not protected' do
+ let(:variable) { create(:ci_group_variable, protected: false) }
+
+ it 'returns the variable' do
+ is_expected.to contain_exactly(variable)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 56817baf79d..6427deda31e 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -5,6 +5,7 @@ describe Ci::PipelineSchedule, models: true do
it { is_expected.to belong_to(:owner) }
it { is_expected.to have_many(:pipelines) }
+ it { is_expected.to have_many(:variables) }
it { is_expected.to respond_to(:ref) }
it { is_expected.to respond_to(:cron) }
@@ -117,4 +118,20 @@ describe Ci::PipelineSchedule, models: true do
end
end
end
+
+ describe '#job_variables' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule) }
+
+ let!(:pipeline_schedule_variables) do
+ create_list(:ci_pipeline_schedule_variable, 2, pipeline_schedule: pipeline_schedule)
+ end
+
+ subject { pipeline_schedule.job_variables }
+
+ before do
+ pipeline_schedule.reload
+ end
+
+ it { is_expected.to contain_exactly(*pipeline_schedule_variables.map(&:to_runner_variable)) }
+ end
end
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
new file mode 100644
index 00000000000..0de76a57b7f
--- /dev/null
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe Ci::PipelineScheduleVariable, models: true do
+ subject { build(:ci_pipeline_schedule_variable) }
+
+ it { is_expected.to include_module(HasVariable) }
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index dab8e8ca432..ba0696fa210 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -672,6 +672,12 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '.internal_sources' do
+ subject { described_class.internal_sources }
+
+ it { is_expected.to be_an(Array) }
+ end
+
describe '#status' do
let(:build) do
create(:ci_build, :created, pipeline: pipeline, name: 'test')
@@ -742,6 +748,39 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#ci_yaml_file_path' do
+ subject { pipeline.ci_yaml_file_path }
+
+ it 'returns the path from project' do
+ allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
+
+ is_expected.to eq('custom/path')
+ end
+
+ it 'returns default when custom path is nil' do
+ allow(pipeline.project).to receive(:ci_config_path) { nil }
+
+ is_expected.to eq('.gitlab-ci.yml')
+ end
+
+ it 'returns default when custom path is empty' do
+ allow(pipeline.project).to receive(:ci_config_path) { '' }
+
+ is_expected.to eq('.gitlab-ci.yml')
+ end
+ end
+
+ describe '#ci_yaml_file' do
+ it 'reports error if the file is not found' do
+ allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
+
+ pipeline.ci_yaml_file
+
+ expect(pipeline.yaml_errors)
+ .to eq('Failed to load CI/CD config file at custom')
+ end
+ end
+
describe '#detailed_status' do
subject { pipeline.detailed_status(user) }
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 83494af24ba..890ffaae494 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -3,14 +3,11 @@ require 'spec_helper'
describe Ci::Variable, models: true do
subject { build(:ci_variable) }
- let(:secret_value) { 'secret' }
-
- it { is_expected.to validate_presence_of(:key) }
- it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) }
- it { is_expected.to validate_length_of(:key).is_at_most(255) }
- it { is_expected.to allow_value('foo').for(:key) }
- it { is_expected.not_to allow_value('foo bar').for(:key) }
- it { is_expected.not_to allow_value('foo/bar').for(:key) }
+ describe 'validations' do
+ it { is_expected.to include_module(HasVariable) }
+ it { is_expected.to include_module(Presentable) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) }
+ end
describe '.unprotected' do
subject { described_class.unprotected }
@@ -33,36 +30,4 @@ describe Ci::Variable, models: true do
end
end
end
-
- describe '#value' do
- before do
- subject.value = secret_value
- end
-
- it 'stores the encrypted value' do
- expect(subject.encrypted_value).not_to be_nil
- end
-
- it 'stores an iv for value' do
- expect(subject.encrypted_value_iv).not_to be_nil
- end
-
- it 'stores a salt for value' do
- expect(subject.encrypted_value_salt).not_to be_nil
- end
-
- it 'fails to decrypt if iv is incorrect' do
- subject.encrypted_value_iv = SecureRandom.hex
- subject.instance_variable_set(:@value, nil)
- expect { subject.value }
- .to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
- end
- end
-
- describe '#to_runner_variable' do
- it 'returns a hash for the runner' do
- expect(subject.to_runner_variable)
- .to eq(key: subject.key, value: subject.value, public: false)
- end
- end
end
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
new file mode 100644
index 00000000000..951690a217b
--- /dev/null
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe EachBatch do
+ describe '.each_batch' do
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ include EachBatch
+
+ self.table_name = 'users'
+ end
+ end
+
+ before do
+ 5.times { create(:user, updated_at: 1.day.ago) }
+ end
+
+ it 'yields an ActiveRecord::Relation when a block is given' do
+ model.each_batch do |relation|
+ expect(relation).to be_a_kind_of(ActiveRecord::Relation)
+ end
+ end
+
+ it 'yields a batch index as the second argument' do
+ model.each_batch do |_, index|
+ expect(index).to eq(1)
+ end
+ end
+
+ it 'accepts a custom batch size' do
+ amount = 0
+
+ model.each_batch(of: 1) { amount += 1 }
+
+ expect(amount).to eq(5)
+ end
+
+ it 'does not include ORDER BYs in the yielded relations' do
+ model.each_batch do |relation|
+ expect(relation.to_sql).not_to include('ORDER BY')
+ end
+ end
+
+ it 'allows updating of the yielded relations' do
+ time = Time.now
+
+ model.each_batch do |relation|
+ relation.update_all(updated_at: time)
+ end
+
+ expect(model.where(updated_at: time).count).to eq(5)
+ end
+ end
+end
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
new file mode 100644
index 00000000000..3f601243245
--- /dev/null
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe FeatureGate do
+ describe 'User' do
+ describe '#flipper_id' do
+ context 'when user is not persisted' do
+ let(:user) { build(:user) }
+
+ it { expect(user.flipper_id).to be_nil }
+ end
+
+ context 'when user is persisted' do
+ let(:user) { create(:user) }
+
+ it { expect(user.flipper_id).to eq "User:#{user.id}" }
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb
new file mode 100644
index 00000000000..f4b24e6d1d9
--- /dev/null
+++ b/spec/models/concerns/has_variable_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe HasVariable do
+ subject { build(:ci_variable) }
+
+ it { is_expected.to validate_presence_of(:key) }
+ it { is_expected.to validate_length_of(:key).is_at_most(255) }
+ it { is_expected.to allow_value('foo').for(:key) }
+ it { is_expected.not_to allow_value('foo bar').for(:key) }
+ it { is_expected.not_to allow_value('foo/bar').for(:key) }
+
+ describe '#value' do
+ before do
+ subject.value = 'secret'
+ end
+
+ it 'stores the encrypted value' do
+ expect(subject.encrypted_value).not_to be_nil
+ end
+
+ it 'stores an iv for value' do
+ expect(subject.encrypted_value_iv).not_to be_nil
+ end
+
+ it 'stores a salt for value' do
+ expect(subject.encrypted_value_salt).not_to be_nil
+ end
+
+ it 'fails to decrypt if iv is incorrect' do
+ subject.encrypted_value_iv = SecureRandom.hex
+ subject.instance_variable_set(:@value, nil)
+ expect { subject.value }
+ .to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
+ end
+ end
+
+ describe '#to_runner_variable' do
+ it 'returns a hash for the runner' do
+ expect(subject.to_runner_variable)
+ .to eq(key: subject.key, value: subject.value, public: false)
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index ac9303370ab..505039c9d88 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -155,7 +155,7 @@ describe Issuable do
end
describe "#sort" do
- let(:project) { build_stubbed(:empty_project) }
+ let(:project) { create(:empty_project) }
context "by milestone due date" do
# Correct order is:
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 65f05121b40..36aedd2f701 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -132,6 +132,19 @@ describe Group, 'Routable' do
end
end
+ describe '#expires_full_path_cache' do
+ context 'with RequestStore active', :request_store do
+ it 'expires the full_path cache' do
+ expect(group.full_path).to eq('foo')
+
+ group.route.update(path: 'bar', name: 'bar')
+ group.expires_full_path_cache
+
+ expect(group.full_path).to eq('bar')
+ end
+ end
+ end
+
describe '#full_name' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
new file mode 100644
index 00000000000..21893e0cbaa
--- /dev/null
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe ShaAttribute do
+ let(:model) { Class.new { include ShaAttribute } }
+
+ before do
+ columns = [
+ double(:column, name: 'name', type: :text),
+ double(:column, name: 'sha1', type: :binary)
+ ]
+
+ allow(model).to receive(:columns).and_return(columns)
+ end
+
+ describe '#sha_attribute' do
+ context 'when the table exists' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(true)
+ end
+
+ it 'defines a SHA attribute for a binary column' do
+ expect(model).to receive(:attribute)
+ .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+
+ model.sha_attribute(:sha1)
+ end
+
+ it 'raises ArgumentError when the column type is not :binary' do
+ expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when the table does not exist' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(false)
+ end
+
+ it 'does nothing' do
+ expect(model).not_to receive(:columns)
+ expect(model).not_to receive(:attribute)
+
+ model.sha_attribute(:name)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
new file mode 100644
index 00000000000..d1e17c4f684
--- /dev/null
+++ b/spec/models/concerns/sortable_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Sortable do
+ let(:relation) { Issue.all }
+
+ describe '#where' do
+ it 'orders by id, descending' do
+ order_node = relation.where(iid: 1).order_values.first
+ expect(order_node).to be_a(Arel::Nodes::Descending)
+ expect(order_node.expr.name).to eq(:id)
+ end
+ end
+
+ describe '#find_by' do
+ it 'does not order' do
+ expect(relation).to receive(:unscope).with(:order).and_call_original
+
+ relation.find_by(iid: 1)
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b0635c6a90a..0a2cd8c2957 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -120,28 +120,17 @@ describe Environment, models: true do
let(:head_commit) { project.commit }
let(:commit) { project.commit.parent }
- context 'Gitaly find_ref_name feature disabled' do
- it 'returns deployment id for the environment' do
- expect(environment.first_deployment_for(commit)).to eq deployment1
- end
+ it 'returns deployment id for the environment' do
+ expect(environment.first_deployment_for(commit)).to eq deployment1
+ end
- it 'return nil when no deployment is found' do
- expect(environment.first_deployment_for(head_commit)).to eq nil
- end
+ it 'return nil when no deployment is found' do
+ expect(environment.first_deployment_for(head_commit)).to eq nil
end
- # TODO: Uncomment when feature is reenabled
- # context 'Gitaly find_ref_name feature enabled' do
- # before do
- # allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
- # end
- #
- # it 'calls GitalyClient' do
- # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
- #
- # environment.first_deployment_for(commit)
- # end
- # end
+ it 'returns a UTF-8 ref' do
+ expect(environment.first_deployment_for(commit).ref).to be_utf8
+ end
end
describe '#environment_type' do
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 6e8d43f988c..38fbdd2536a 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -2,53 +2,75 @@ require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do
let(:project_from) { create(:project, :repository) }
+ let(:project_to) { fork_project(project_from, user) }
let(:user) { create(:user) }
let(:namespace) { user.namespace }
before do
- create(:project_member, :reporter, user: user, project: project_from)
- @project_to = fork_project(project_from, user)
+ project_from.add_reporter(user)
+ end
+
+ it 'project_from knows its forks' do
+ _ = project_to
+
+ expect(project_from.forks.count).to eq(1)
end
it "project_to knows it is forked" do
- expect(@project_to.forked?).to be_truthy
+ expect(project_to.forked?).to be_truthy
end
it "project knows who it is forked from" do
- expect(@project_to.forked_from_project).to eq(project_from)
+ expect(project_to.forked_from_project).to eq(project_from)
end
-end
-describe '#forked?' do
- let(:forked_project_link) { build(:forked_project_link) }
- let(:project_from) { create(:project, :repository) }
- let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+ context 'project_to is pending_delete' do
+ before do
+ project_to.update!(pending_delete: true)
+ end
- before :each do
- forked_project_link.forked_from_project = project_from
- forked_project_link.forked_to_project = project_to
- forked_project_link.save!
+ it { expect(project_from.forks.count).to eq(0) }
end
- it "project_to knows it is forked" do
- expect(project_to.forked?).to be_truthy
- end
+ context 'project_from is pending_delete' do
+ before do
+ project_from.update!(pending_delete: true)
+ end
- it "project_from is not forked" do
- expect(project_from.forked?).to be_falsey
+ it { expect(project_to.forked_from_project).to be_nil }
end
- it "project_to.destroy destroys fork_link" do
- expect(forked_project_link).to receive(:destroy)
- project_to.destroy
+ describe '#forked?' do
+ let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+ let(:forked_project_link) { create(:forked_project_link) }
+
+ before do
+ forked_project_link.forked_from_project = project_from
+ forked_project_link.forked_to_project = project_to
+ forked_project_link.save!
+ end
+
+ it "project_to knows it is forked" do
+ expect(project_to.forked?).to be_truthy
+ end
+
+ it "project_from is not forked" do
+ expect(project_from.forked?).to be_falsey
+ end
+
+ it "project_to.destroy destroys fork_link" do
+ project_to.destroy
+
+ expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
+ end
end
-end
-def fork_project(from_project, user)
- shell = double('gitlab_shell', fork_repository: true)
+ def fork_project(from_project, user)
+ service = Projects::ForkService.new(from_project, user)
+ shell = double('gitlab_shell', fork_repository: true)
- service = Projects::ForkService.new(from_project, user)
- allow(service).to receive(:gitlab_shell).and_return(shell)
+ allow(service).to receive(:gitlab_shell).and_return(shell)
- service.execute
+ service.execute
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4de1683b21c..066d7b9307f 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -13,6 +13,7 @@ describe Group, models: true do
it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
it { is_expected.to have_many(:labels).class_name('GroupLabel') }
+ it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_one(:chat_team) }
@@ -418,4 +419,69 @@ describe Group, models: true do
expect(calls).to eq 2
end
end
+
+ describe '#secret_variables_for' do
+ let(:project) { create(:empty_project, group: group) }
+
+ let!(:secret_variable) do
+ create(:ci_group_variable, value: 'secret', group: group)
+ end
+
+ let!(:protected_variable) do
+ create(:ci_group_variable, :protected, value: 'protected', group: group)
+ end
+
+ subject { group.secret_variables_for('ref', project) }
+
+ shared_examples 'ref is protected' do
+ it 'contains all the variables' do
+ is_expected.to contain_exactly(secret_variable, protected_variable)
+ end
+ end
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'contains only the secret variables' do
+ is_expected.to contain_exactly(secret_variable)
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when group has children', :postgresql do
+ let(:group_child) { create(:group, parent: group) }
+ let(:group_child_2) { create(:group, parent: group_child) }
+ let(:group_child_3) { create(:group, parent: group_child_2) }
+ let(:variable_child) { create(:ci_group_variable, group: group_child) }
+ let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
+ let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
+
+ it 'returns all variables belong to the group and parent groups' do
+ expected_array1 = [protected_variable, secret_variable]
+ expected_array2 = [variable_child, variable_child_2, variable_child_3]
+ got_array = group_child_3.secret_variables_for('ref', project).to_a
+
+ expect(got_array.shift(2)).to contain_exactly(*expected_array1)
+ expect(got_array).to eq(expected_array2)
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
new file mode 100644
index 00000000000..dbfd1526518
--- /dev/null
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe MergeRequestDiffCommit, type: :model do
+ let(:merge_request) { create(:merge_request) }
+ subject { merge_request.commits.first }
+
+ describe '#to_hash' do
+ it 'returns the same results as Commit#to_hash, except for parent_ids' do
+ commit_from_repo = merge_request.project.repository.commit(subject.sha)
+ commit_from_repo_hash = commit_from_repo.to_hash.merge(parent_ids: [])
+
+ expect(subject.to_hash).to eq(commit_from_repo_hash)
+ end
+ end
+end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 4ad4abaa572..edc2f4bb9f0 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -98,7 +98,7 @@ describe MergeRequestDiff, models: true do
end
it 'saves empty state' do
- allow_any_instance_of(MergeRequestDiff).to receive(:commits)
+ allow_any_instance_of(MergeRequestDiff).to receive_message_chain(:compare, :commits)
.and_return([])
mr_diff = create(:merge_request).merge_request_diff
@@ -107,14 +107,14 @@ describe MergeRequestDiff, models: true do
end
end
- describe '#commits_sha' do
+ describe '#commit_shas' do
it 'returns all commits SHA using serialized commits' do
subject.st_commits = [
{ id: 'sha1' },
{ id: 'sha2' }
]
- expect(subject.commits_sha).to eq(%w(sha1 sha2))
+ expect(subject.commit_shas).to eq(%w(sha1 sha2))
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index bb5273074a2..1eadc28869f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -10,7 +10,7 @@ describe MergeRequest, models: true do
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
it { is_expected.to belong_to(:assignee) }
- it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) }
+ it { is_expected.to have_many(:merge_request_diffs) }
end
describe 'modules' do
@@ -105,6 +105,22 @@ describe MergeRequest, models: true do
end
end
+ describe '#assignee_ids' do
+ it 'returns an array of the assigned user id' do
+ subject.assignee_id = 123
+
+ expect(subject.assignee_ids).to eq([123])
+ end
+ end
+
+ describe '#assignee_ids=' do
+ it 'sets assignee_id to the last id in the array' do
+ subject.assignee_ids = [123, 456]
+
+ expect(subject.assignee_id).to eq(456)
+ end
+ end
+
describe '#assignee_or_author?' do
let(:user) { create(:user) }
@@ -704,14 +720,14 @@ describe MergeRequest, models: true do
subject { create :merge_request, :simple }
end
- describe '#commits_sha' do
+ describe '#commit_shas' do
before do
- allow(subject.merge_request_diff).to receive(:commits_sha)
+ allow(subject.merge_request_diff).to receive(:commit_shas)
.and_return(['sha1'])
end
it 'delegates to merge request diff' do
- expect(subject.commits_sha).to eq ['sha1']
+ expect(subject.commit_shas).to eq ['sha1']
end
end
@@ -736,7 +752,7 @@ describe MergeRequest, models: true do
describe '#all_pipelines' do
shared_examples 'returning pipelines with proper ordering' do
let!(:all_pipelines) do
- subject.all_commits_sha.map do |sha|
+ subject.all_commit_shas.map do |sha|
create(:ci_empty_pipeline,
project: subject.source_project,
sha: sha,
@@ -778,16 +794,16 @@ describe MergeRequest, models: true do
end
end
- describe '#all_commits_sha' do
+ describe '#all_commit_shas' do
context 'when merge request is persisted' do
- let(:all_commits_sha) do
+ let(:all_commit_shas) do
subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
end
shared_examples 'returning all SHA' do
it 'returns all SHA from all merge_request_diffs' do
expect(subject.merge_request_diffs.size).to eq(2)
- expect(subject.all_commits_sha).to eq(all_commits_sha)
+ expect(subject.all_commit_shas).to match_array(all_commit_shas)
end
end
@@ -818,7 +834,7 @@ describe MergeRequest, models: true do
end
it 'returns commits from compare commits temporary data' do
- expect(subject.all_commits_sha).to eq [commit, commit]
+ expect(subject.all_commit_shas).to eq [commit, commit]
end
end
@@ -826,7 +842,7 @@ describe MergeRequest, models: true do
subject { build(:merge_request) }
it 'returns array with diff head sha element only' do
- expect(subject.all_commits_sha).to eq [subject.diff_head_sha]
+ expect(subject.all_commit_shas).to eq [subject.diff_head_sha]
end
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 45953023a36..2649d04bee3 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -6,9 +6,6 @@ describe Milestone, models: true do
allow(subject).to receive(:set_iid).and_return(false)
end
- it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to validate_presence_of(:project) }
-
describe 'start_date' do
it 'adds an error when start_date is greated then due_date' do
milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)
@@ -37,17 +34,42 @@ describe Milestone, models: true do
end
end
- describe "unique milestone title per project" do
- it "does not accept the same title in a project twice" do
- new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
- expect(new_milestone).not_to be_valid
+ describe "unique milestone title" do
+ context "per project" do
+ it "does not accept the same title in a project twice" do
+ new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
+ expect(new_milestone).not_to be_valid
+ end
+
+ it "accepts the same title in another project" do
+ project = create(:empty_project)
+ new_milestone = Milestone.new(project: project, title: milestone.title)
+
+ expect(new_milestone).to be_valid
+ end
end
- it "accepts the same title in another project" do
- project = build(:empty_project)
- new_milestone = Milestone.new(project: project, title: milestone.title)
+ context "per group" do
+ let(:group) { create(:group) }
+ let(:milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(group: group)
+ end
+
+ it "does not accept the same title in a group twice" do
+ new_milestone = Milestone.new(group: group, title: milestone.title)
+
+ expect(new_milestone).not_to be_valid
+ end
- expect(new_milestone).to be_valid
+ it "does not accept the same title of a child project milestone" do
+ create(:milestone, project: group.projects.first)
+
+ new_milestone = Milestone.new(group: group, title: milestone.title)
+
+ expect(new_milestone).not_to be_valid
+ end
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e7c3acf19eb..62c4ea01ce1 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -323,6 +323,36 @@ describe Namespace, models: true do
end
end
+ describe '#users_with_descendants', :nested_groups do
+ let(:user_a) { create(:user) }
+ let(:user_b) { create(:user) }
+
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:deep_nested_group) { create(:group, parent: nested_group) }
+
+ it 'returns member users on every nest level without duplication' do
+ group.add_developer(user_a)
+ nested_group.add_developer(user_b)
+ deep_nested_group.add_developer(user_a)
+
+ expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
+ expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
+ expect(deep_nested_group.users_with_descendants).to contain_exactly(user_a)
+ end
+ end
+
+ describe '#soft_delete_without_removing_associations' do
+ let(:project1) { create(:project_empty_repo, namespace: namespace) }
+
+ it 'updates the deleted_at timestamp but preserves projects' do
+ namespace.soft_delete_without_removing_associations
+
+ expect(Project.all).to include(project1)
+ expect(namespace.deleted_at).not_to be_nil
+ end
+ end
+
describe '#user_ids_for_project_authorizations' do
it 'returns the user IDs for which to refresh authorizations' do
expect(namespace.user_ids_for_project_authorizations)
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 4161b9158b1..d68d8b719cd 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe ProjectGroupLink do
describe "Associations" do
- it { should belong_to(:group) }
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:project) }
end
describe "Validation" do
@@ -12,10 +12,10 @@ describe ProjectGroupLink do
let(:project) { create(:project, group: group) }
let!(:project_group_link) { create(:project_group_link, project: project) }
- it { should validate_presence_of(:project_id) }
- it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
- it { should validate_presence_of(:group) }
- it { should validate_presence_of(:group_access) }
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
+ it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_presence_of(:group_access) }
it "doesn't allow a project to be shared with the group it is in" do
project_group_link.group = group
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 291fc645a1c..ef10df9e092 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
describe ExternalWikiService, models: true do
include ExternalWikiHelper
describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index c86f56c55eb..105afed1337 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe JiraService, models: true do
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
describe "Associations" do
it { is_expected.to belong_to :project }
@@ -64,12 +64,12 @@ describe JiraService, models: true do
end
end
- describe '#reference_pattern' do
+ describe '.reference_pattern' do
it_behaves_like 'allows project key on reference pattern'
it 'does not allow # on the code' do
- expect(subject.reference_pattern.match('#123')).to be_nil
- expect(subject.reference_pattern.match('1#23#12')).to be_nil
+ expect(described_class.reference_pattern.match('#123')).to be_nil
+ expect(described_class.reference_pattern.match('1#23#12')).to be_nil
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 858ad595dbf..5ba523a478a 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -129,7 +129,7 @@ describe KubernetesService, models: true, caching: true do
it "returns the default namespace" do
is_expected.to eq(service.send(:default_namespace))
end
-
+
context 'when namespace is specified' do
before do
service.namespace = 'my-namespace'
@@ -201,6 +201,22 @@ describe KubernetesService, models: true, caching: true do
end
describe '#predefined_variables' do
+ let(:kubeconfig) do
+ config =
+ YAML.load(File.read(expand_fixture_path('config/kubeconfig.yml')))
+
+ config.dig('users', 0, 'user')['token'] =
+ 'token'
+
+ config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
+ Base64.encode64('CA PEM DATA')
+
+ config.dig('contexts', 0, 'context')['namespace'] =
+ namespace
+
+ YAML.dump(config)
+ end
+
before do
subject.api_url = 'https://kube.domain.com'
subject.token = 'token'
@@ -208,32 +224,34 @@ describe KubernetesService, models: true, caching: true do
subject.project = project
end
- context 'namespace is provided' do
- before do
- subject.namespace = 'my-project'
- end
-
+ shared_examples 'setting variables' do
it 'sets the variables' do
expect(subject.predefined_variables).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false },
- { key: 'KUBE_NAMESPACE', value: 'my-project', public: true },
+ { key: 'KUBE_NAMESPACE', value: namespace, public: true },
+ { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
{ key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
)
end
end
- context 'no namespace provided' do
- it 'sets the variables' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
- { key: 'KUBE_TOKEN', value: 'token', public: false },
- { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
- )
+ context 'namespace is provided' do
+ let(:namespace) { 'my-project' }
+
+ before do
+ subject.namespace = namespace
end
+ it_behaves_like 'setting variables'
+ end
+
+ context 'no namespace provided' do
+ let(:namespace) { subject.actual_namespace }
+
+ it_behaves_like 'setting variables'
+
it 'sets the KUBE_NAMESPACE' do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 6631d9040b1..441b3f896ca 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -31,11 +31,11 @@ describe RedmineService, models: true do
end
end
- describe '#reference_pattern' do
+ describe '.reference_pattern' do
it_behaves_like 'allows project key on reference pattern'
it 'does allow # on the reference' do
- expect(subject.reference_pattern.match('#123')[:issue]).to eq('123')
+ expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d7fcadb895e..99bfab70088 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -7,50 +7,50 @@ describe Project, models: true do
it { is_expected.to belong_to(:creator).class_name('User') }
it { is_expected.to have_many(:users) }
it { is_expected.to have_many(:services) }
- it { is_expected.to have_many(:events).dependent(:destroy) }
- it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
- it { is_expected.to have_many(:issues).dependent(:destroy) }
- it { is_expected.to have_many(:milestones).dependent(:destroy) }
- it { is_expected.to have_many(:project_members).dependent(:destroy) }
+ it { is_expected.to have_many(:events) }
+ it { is_expected.to have_many(:merge_requests) }
+ it { is_expected.to have_many(:issues) }
+ it { is_expected.to have_many(:milestones) }
+ it { is_expected.to have_many(:project_members).dependent(:delete_all) }
it { is_expected.to have_many(:users).through(:project_members) }
- it { is_expected.to have_many(:requesters).dependent(:destroy) }
- it { is_expected.to have_many(:notes).dependent(:destroy) }
- it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
- it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) }
+ it { is_expected.to have_many(:requesters).dependent(:delete_all) }
+ it { is_expected.to have_many(:notes) }
+ it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') }
+ it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:deploy_keys) }
- it { is_expected.to have_many(:hooks).dependent(:destroy) }
- it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
- it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
- it { is_expected.to have_one(:slack_service).dependent(:destroy) }
- it { is_expected.to have_one(:microsoft_teams_service).dependent(:destroy) }
- it { is_expected.to have_one(:mattermost_service).dependent(:destroy) }
- it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
- it { is_expected.to have_one(:asana_service).dependent(:destroy) }
- it { is_expected.to have_many(:boards).dependent(:destroy) }
- it { is_expected.to have_one(:campfire_service).dependent(:destroy) }
- it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) }
- it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
- it { is_expected.to have_one(:pipelines_email_service).dependent(:destroy) }
- it { is_expected.to have_one(:irker_service).dependent(:destroy) }
- it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
- it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
- it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
- it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) }
- it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) }
- it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
- it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
- it { is_expected.to have_one(:bamboo_service).dependent(:destroy) }
- it { is_expected.to have_one(:teamcity_service).dependent(:destroy) }
- it { is_expected.to have_one(:jira_service).dependent(:destroy) }
- it { is_expected.to have_one(:redmine_service).dependent(:destroy) }
- it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) }
- it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
- it { is_expected.to have_one(:project_feature).dependent(:destroy) }
- it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) }
- it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) }
+ it { is_expected.to have_many(:hooks) }
+ it { is_expected.to have_many(:protected_branches) }
+ it { is_expected.to have_one(:forked_project_link) }
+ it { is_expected.to have_one(:slack_service) }
+ it { is_expected.to have_one(:microsoft_teams_service) }
+ it { is_expected.to have_one(:mattermost_service) }
+ it { is_expected.to have_one(:pushover_service) }
+ it { is_expected.to have_one(:asana_service) }
+ it { is_expected.to have_many(:boards) }
+ it { is_expected.to have_one(:campfire_service) }
+ it { is_expected.to have_one(:drone_ci_service) }
+ it { is_expected.to have_one(:emails_on_push_service) }
+ it { is_expected.to have_one(:pipelines_email_service) }
+ it { is_expected.to have_one(:irker_service) }
+ it { is_expected.to have_one(:pivotaltracker_service) }
+ it { is_expected.to have_one(:hipchat_service) }
+ it { is_expected.to have_one(:flowdock_service) }
+ it { is_expected.to have_one(:assembla_service) }
+ it { is_expected.to have_one(:slack_slash_commands_service) }
+ it { is_expected.to have_one(:mattermost_slash_commands_service) }
+ it { is_expected.to have_one(:gemnasium_service) }
+ it { is_expected.to have_one(:buildkite_service) }
+ it { is_expected.to have_one(:bamboo_service) }
+ it { is_expected.to have_one(:teamcity_service) }
+ it { is_expected.to have_one(:jira_service) }
+ it { is_expected.to have_one(:redmine_service) }
+ it { is_expected.to have_one(:custom_issue_tracker_service) }
+ it { is_expected.to have_one(:bugzilla_service) }
+ it { is_expected.to have_one(:gitlab_issue_tracker_service) }
+ it { is_expected.to have_one(:external_wiki_service) }
+ it { is_expected.to have_one(:project_feature) }
+ it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
+ it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
it { is_expected.to have_many(:commit_statuses) }
@@ -62,18 +62,18 @@ describe Project, models: true do
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
it { is_expected.to have_many(:pages_domains) }
- it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) }
- it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
- it { is_expected.to have_many(:environments).dependent(:destroy) }
- it { is_expected.to have_many(:deployments).dependent(:destroy) }
- it { is_expected.to have_many(:todos).dependent(:destroy) }
- it { is_expected.to have_many(:releases).dependent(:destroy) }
- it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) }
- it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
- it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
+ it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
+ it { is_expected.to have_many(:users_star_projects) }
+ it { is_expected.to have_many(:environments) }
+ it { is_expected.to have_many(:deployments) }
+ it { is_expected.to have_many(:todos) }
+ it { is_expected.to have_many(:releases) }
+ it { is_expected.to have_many(:lfs_objects_projects) }
+ it { is_expected.to have_many(:project_group_links) }
+ it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
it { is_expected.to have_many(:forks).through(:forked_project_links) }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
- it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) }
+ it { is_expected.to have_many(:pipeline_schedules) }
context 'after initialized' do
it "has a project_feature" do
@@ -143,6 +143,10 @@ describe Project, models: true do
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
+ it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
+ it { is_expected.to allow_value('').for(:ci_config_path) }
+ it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
+
it { is_expected.to validate_presence_of(:creator) }
it { is_expected.to validate_presence_of(:namespace) }
@@ -284,15 +288,6 @@ describe Project, models: true do
end
end
- describe 'default_scope' do
- it 'excludes projects pending deletion from the results' do
- project = create(:empty_project)
- create(:empty_project, pending_delete: true)
-
- expect(Project.all).to eq [project]
- end
- end
-
describe 'project token' do
it 'sets an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
@@ -832,13 +827,13 @@ describe Project, models: true do
let(:avatar_path) { "/#{project.full_path}/avatar" }
- it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+ it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
end
context 'when git repo is empty' do
let(:project) { create(:empty_project) }
- it { should eq nil }
+ it { is_expected.to eq nil }
end
end
@@ -1179,6 +1174,16 @@ describe Project, models: true do
expect(relation.search(project.namespace.name)).to eq([project])
end
+
+ describe 'with pending_delete project' do
+ let(:pending_delete_project) { create(:empty_project, pending_delete: true) }
+
+ it 'shows pending deletion project' do
+ search_result = described_class.search(pending_delete_project.name)
+
+ expect(search_result).to eq([pending_delete_project])
+ end
+ end
end
describe '#rename_repo' do
@@ -1215,6 +1220,8 @@ describe Project, models: true do
expect(project).to receive(:expire_caches_before_rename)
+ expect(project).to receive(:expires_full_path_cache)
+
project.rename_repo
end
@@ -1327,6 +1334,50 @@ describe Project, models: true do
end
end
+ describe '#ensure_repository' do
+ let(:project) { create(:project, :repository) }
+ let(:shell) { Gitlab::Shell.new }
+
+ before do
+ allow(project).to receive(:gitlab_shell).and_return(shell)
+ end
+
+ it 'creates the repository if it not exist' do
+ allow(project).to receive(:repository_exists?)
+ .and_return(false)
+
+ allow(shell).to receive(:add_repository)
+ .with(project.repository_storage_path, project.path_with_namespace)
+ .and_return(true)
+
+ expect(project).to receive(:create_repository).with(force: true)
+
+ project.ensure_repository
+ end
+
+ it 'does not create the repository if it exists' do
+ allow(project).to receive(:repository_exists?)
+ .and_return(true)
+
+ expect(project).not_to receive(:create_repository)
+
+ project.ensure_repository
+ end
+
+ it 'creates the repository if it is a fork' do
+ expect(project).to receive(:forked?).and_return(true)
+
+ allow(project).to receive(:repository_exists?)
+ .and_return(false)
+
+ expect(shell).to receive(:add_repository)
+ .with(project.repository_storage_path, project.path_with_namespace)
+ .and_return(true)
+
+ project.ensure_repository
+ end
+ end
+
describe '#user_can_push_to_empty_repo?' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -1457,6 +1508,28 @@ describe Project, models: true do
end
end
+ describe '#ci_config_path=' do
+ let(:project) { create(:empty_project) }
+
+ it 'sets nil' do
+ project.update!(ci_config_path: nil)
+
+ expect(project.ci_config_path).to be_nil
+ end
+
+ it 'sets a string' do
+ project.update!(ci_config_path: 'foo/.gitlab_ci.yml')
+
+ expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
+ end
+
+ it 'sets a string but removes all leading slashes and null characters' do
+ project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml")
+
+ expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
+ end
+ end
+
describe 'Project import job' do
let(:project) { create(:empty_project, import_url: generate(:url)) }
@@ -1478,6 +1551,40 @@ describe Project, models: true do
end
end
+ describe 'project import state transitions' do
+ context 'state transition: [:started] => [:finished]' do
+ let(:housekeeping_service) { spy }
+
+ before do
+ allow(Projects::HousekeepingService).to receive(:new) { housekeeping_service }
+ end
+
+ it 'performs housekeeping when an import of a fresh project is completed' do
+ project = create(:project_empty_repo, :import_started, import_type: :github)
+
+ project.import_finish
+
+ expect(housekeeping_service).to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project repository does not exist' do
+ project = create(:empty_project, :import_started, import_type: :github)
+
+ project.import_finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project does not have a valid import type' do
+ project = create(:empty_project, :import_started, import_type: nil)
+
+ project.import_finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+ end
+ end
+
describe '#latest_successful_builds_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
@@ -1768,7 +1875,12 @@ describe Project, models: true do
create(:ci_variable, :protected, value: 'protected', project: project)
end
- subject { project.secret_variables_for('ref') }
+ subject { project.secret_variables_for(ref: 'ref') }
+
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
shared_examples 'ref is protected' do
it 'contains all the variables' do
@@ -1777,11 +1889,6 @@ describe Project, models: true do
end
context 'when the ref is not protected' do
- before do
- stub_application_setting(
- default_branch_protection: Gitlab::Access::PROTECTION_NONE)
- end
-
it 'contains only the secret variables' do
is_expected.to contain_exactly(secret_variable)
end
@@ -2092,4 +2199,21 @@ describe Project, models: true do
end
end
end
+
+ describe '#remove_private_deploy_keys' do
+ it 'removes the private deploy keys of a project' do
+ project = create(:empty_project)
+
+ private_key = create(:deploy_key, public: false)
+ public_key = create(:deploy_key, public: true)
+
+ create(:deploy_keys_project, deploy_key: private_key, project: project)
+ create(:deploy_keys_project, deploy_key: public_key, project: project)
+
+ project.remove_private_deploy_keys
+
+ expect(project.deploy_keys.where(public: false).any?).to eq(false)
+ expect(project.deploy_keys.where(public: true).any?).to eq(true)
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index bf74ac5ea25..1f314791479 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -278,6 +278,24 @@ describe ProjectWiki, models: true do
end
end
+ describe '#ensure_repository' do
+ it 'creates the repository if it not exist' do
+ allow(subject).to receive(:repository_exists?).and_return(false)
+
+ expect(subject).to receive(:create_repo!)
+
+ subject.ensure_repository
+ end
+
+ it 'does not create the repository if it exists' do
+ allow(subject).to receive(:repository_exists?).and_return(true)
+
+ expect(subject).not_to receive(:create_repo!)
+
+ subject.ensure_repository
+ end
+ end
+
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 3e984ec7588..af305e9b234 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -347,6 +347,17 @@ describe Repository, models: true do
expect(blob.data).to eq('Changelog!')
end
+ it 'creates new file and dir when file_path has a forward slash' do
+ expect do
+ repository.create_file(user, 'new_dir/new_file.txt', 'File!',
+ message: 'Create new_file with new_dir',
+ branch_name: 'master')
+ end.to change { repository.commits('master').count }.by(1)
+
+ expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
+ expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
+ end
+
it 'respects the autocrlf setting' do
repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
message: 'Add hello world',
@@ -780,7 +791,7 @@ describe Repository, models: true do
context 'when pre hooks were successful' do
it 'runs without errors' do
expect_any_instance_of(GitHooksService).to receive(:execute)
- .with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature')
+ .with(user, project, old_rev, blank_sha, 'refs/heads/feature')
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
end
@@ -823,12 +834,7 @@ describe Repository, models: true do
service = GitHooksService.new
expect(GitHooksService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
- .with(
- user,
- repository.path_to_repo,
- old_rev,
- new_rev,
- 'refs/heads/feature')
+ .with(user, project, old_rev, new_rev, 'refs/heads/feature')
.and_yield(service).and_return(true)
end
@@ -1474,9 +1480,9 @@ describe Repository, models: true do
it 'passes commit SHA to pre-receive and update hooks,\
and tag SHA to post-receive hook' do
- pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo)
- update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo)
- post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo)
+ pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project)
+ update_hook = Gitlab::Git::Hook.new('update', project)
+ post_receive_hook = Gitlab::Git::Hook.new('post-receive', project)
allow(Gitlab::Git::Hook).to receive(:new)
.and_return(pre_receive_hook, update_hook, post_receive_hook)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8e895ec6634..448555d2190 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -754,42 +754,49 @@ describe User, models: true do
end
describe '.search' do
- let(:user) { create(:user) }
+ let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') }
+ let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') }
- it 'returns users with a matching name' do
- expect(described_class.search(user.name)).to eq([user])
- end
+ describe 'name matching' do
+ it 'returns users with a matching name with exact match first' do
+ expect(described_class.search(user.name)).to eq([user, user2])
+ end
- it 'returns users with a partially matching name' do
- expect(described_class.search(user.name[0..2])).to eq([user])
- end
+ it 'returns users with a partially matching name' do
+ expect(described_class.search(user.name[0..2])).to eq([user2, user])
+ end
- it 'returns users with a matching name regardless of the casing' do
- expect(described_class.search(user.name.upcase)).to eq([user])
+ it 'returns users with a matching name regardless of the casing' do
+ expect(described_class.search(user2.name.upcase)).to eq([user2])
+ end
end
- it 'returns users with a matching Email' do
- expect(described_class.search(user.email)).to eq([user])
- end
+ describe 'email matching' do
+ it 'returns users with a matching Email' do
+ expect(described_class.search(user.email)).to eq([user, user2])
+ end
- it 'returns users with a partially matching Email' do
- expect(described_class.search(user.email[0..2])).to eq([user])
- end
+ it 'returns users with a partially matching Email' do
+ expect(described_class.search(user.email[0..2])).to eq([user2, user])
+ end
- it 'returns users with a matching Email regardless of the casing' do
- expect(described_class.search(user.email.upcase)).to eq([user])
+ it 'returns users with a matching Email regardless of the casing' do
+ expect(described_class.search(user2.email.upcase)).to eq([user2])
+ end
end
- it 'returns users with a matching username' do
- expect(described_class.search(user.username)).to eq([user])
- end
+ describe 'username matching' do
+ it 'returns users with a matching username' do
+ expect(described_class.search(user.username)).to eq([user, user2])
+ end
- it 'returns users with a partially matching username' do
- expect(described_class.search(user.username[0..2])).to eq([user])
- end
+ it 'returns users with a partially matching username' do
+ expect(described_class.search(user.username[0..2])).to eq([user2, user])
+ end
- it 'returns users with a matching username regardless of the casing' do
- expect(described_class.search(user.username.upcase)).to eq([user])
+ it 'returns users with a matching username regardless of the casing' do
+ expect(described_class.search(user2.username.upcase)).to eq([user2])
+ end
end
end
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 02acdcb36df..e1963091a72 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe BasePolicy, models: true do
describe '.class_for' do
it 'detects policy class based on the subject ancestors' do
- expect(described_class.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
+ expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
end
it 'detects policy class for a presented subject' do
presentee = Ci::BuildPresenter.new(Ci::Build.new)
- expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy)
+ expect(DeclarativePolicy.class_for(presentee)).to eq(Ci::BuildPolicy)
end
it 'uses GlobalPolicy when :global is given' do
- expect(described_class.class_for(:global)).to eq(GlobalPolicy)
+ expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy)
end
end
end
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 48a139d4b83..ace95ac7067 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -5,8 +5,8 @@ describe Ci::BuildPolicy, :models do
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- let(:policies) do
- described_class.abilities(user, build).to_set
+ let(:policy) do
+ described_class.new(user, build)
end
shared_context 'public pipelines disabled' do
@@ -21,7 +21,7 @@ describe Ci::BuildPolicy, :models do
context 'when public builds are enabled' do
it 'does not include ability to read build' do
- expect(policies).not_to include :read_build
+ expect(policy).not_to be_allowed :read_build
end
end
@@ -29,7 +29,7 @@ describe Ci::BuildPolicy, :models do
include_context 'public pipelines disabled'
it 'does not include ability to read build' do
- expect(policies).not_to include :read_build
+ expect(policy).not_to be_allowed :read_build
end
end
end
@@ -39,7 +39,7 @@ describe Ci::BuildPolicy, :models do
context 'when public builds are enabled' do
it 'includes ability to read build' do
- expect(policies).to include :read_build
+ expect(policy).to be_allowed :read_build
end
end
@@ -47,7 +47,7 @@ describe Ci::BuildPolicy, :models do
include_context 'public pipelines disabled'
it 'does not include ability to read build' do
- expect(policies).not_to include :read_build
+ expect(policy).not_to be_allowed :read_build
end
end
end
@@ -62,7 +62,7 @@ describe Ci::BuildPolicy, :models do
context 'when public builds are enabled' do
it 'includes ability to read build' do
- expect(policies).to include :read_build
+ expect(policy).to be_allowed :read_build
end
end
@@ -70,7 +70,7 @@ describe Ci::BuildPolicy, :models do
include_context 'public pipelines disabled'
it 'does not include ability to read build' do
- expect(policies).not_to include :read_build
+ expect(policy).not_to be_allowed :read_build
end
end
end
@@ -82,7 +82,7 @@ describe Ci::BuildPolicy, :models do
context 'when public builds are enabled' do
it 'includes ability to read build' do
- expect(policies).to include :read_build
+ expect(policy).to be_allowed :read_build
end
end
@@ -90,7 +90,7 @@ describe Ci::BuildPolicy, :models do
include_context 'public pipelines disabled'
it 'does not include ability to read build' do
- expect(policies).to include :read_build
+ expect(policy).to be_allowed :read_build
end
end
end
@@ -115,7 +115,7 @@ describe Ci::BuildPolicy, :models do
end
it 'does not include ability to update build' do
- expect(policies).not_to include :update_build
+ expect(policy).to be_disallowed :update_build
end
end
@@ -125,7 +125,7 @@ describe Ci::BuildPolicy, :models do
end
it 'includes ability to update build' do
- expect(policies).to include :update_build
+ expect(policy).to be_allowed :update_build
end
end
end
@@ -135,7 +135,7 @@ describe Ci::BuildPolicy, :models do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
it 'includes ability to update build' do
- expect(policies).to include :update_build
+ expect(policy).to be_allowed :update_build
end
end
@@ -143,7 +143,7 @@ describe Ci::BuildPolicy, :models do
let(:build) { create(:ci_build, pipeline: pipeline) }
it 'includes ability to update build' do
- expect(policies).to include :update_build
+ expect(policy).to be_allowed :update_build
end
end
end
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index 63ad5eb7322..ed4010e723b 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -6,36 +6,36 @@ describe Ci::TriggerPolicy, :models do
let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
let(:policies) do
- described_class.abilities(user, trigger).to_set
+ described_class.new(user, trigger)
end
shared_examples 'allows to admin and manage trigger' do
it 'does include ability to admin trigger' do
- expect(policies).to include :admin_trigger
+ expect(policies).to be_allowed :admin_trigger
end
it 'does include ability to manage trigger' do
- expect(policies).to include :manage_trigger
+ expect(policies).to be_allowed :manage_trigger
end
end
shared_examples 'allows to manage trigger' do
it 'does not include ability to admin trigger' do
- expect(policies).not_to include :admin_trigger
+ expect(policies).not_to be_allowed :admin_trigger
end
it 'does include ability to manage trigger' do
- expect(policies).to include :manage_trigger
+ expect(policies).to be_allowed :manage_trigger
end
end
shared_examples 'disallows to admin and manage trigger' do
it 'does not include ability to admin trigger' do
- expect(policies).not_to include :admin_trigger
+ expect(policies).not_to be_allowed :admin_trigger
end
it 'does not include ability to manage trigger' do
- expect(policies).not_to include :manage_trigger
+ expect(policies).not_to be_allowed :manage_trigger
end
end
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index 28e10f0bfe2..f15f4a11f02 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe DeployKeyPolicy, models: true do
- subject { described_class.abilities(current_user, deploy_key).to_set }
+ subject { described_class.new(current_user, deploy_key) }
describe 'updating a deploy_key' do
context 'when a regular user' do
@@ -16,7 +16,7 @@ describe DeployKeyPolicy, models: true do
project.deploy_keys << deploy_key
end
- it { is_expected.to include(:update_deploy_key) }
+ it { is_expected.to be_allowed(:update_deploy_key) }
end
context 'tries to update private deploy key attached to other project' do
@@ -27,13 +27,13 @@ describe DeployKeyPolicy, models: true do
other_project.deploy_keys << deploy_key
end
- it { is_expected.not_to include(:update_deploy_key) }
+ it { is_expected.to be_disallowed(:update_deploy_key) }
end
context 'tries to update public deploy key' do
let(:deploy_key) { create(:another_deploy_key, public: true) }
- it { is_expected.not_to include(:update_deploy_key) }
+ it { is_expected.to be_disallowed(:update_deploy_key) }
end
end
@@ -43,13 +43,13 @@ describe DeployKeyPolicy, models: true do
context ' tries to update private deploy key' do
let(:deploy_key) { create(:deploy_key, public: false) }
- it { is_expected.to include(:update_deploy_key) }
+ it { is_expected.to be_allowed(:update_deploy_key) }
end
context 'when an admin user tries to update public deploy key' do
let(:deploy_key) { create(:another_deploy_key, public: true) }
- it { is_expected.to include(:update_deploy_key) }
+ it { is_expected.to be_allowed(:update_deploy_key) }
end
end
end
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 650432520bb..035e20c7452 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -8,8 +8,8 @@ describe EnvironmentPolicy do
create(:environment, :with_review_app, project: project)
end
- let(:policies) do
- described_class.abilities(user, environment).to_set
+ let(:policy) do
+ described_class.new(user, environment)
end
describe '#rules' do
@@ -17,7 +17,7 @@ describe EnvironmentPolicy do
let(:project) { create(:project, :private) }
it 'does not include ability to stop environment' do
- expect(policies).not_to include :stop_environment
+ expect(policy).to be_disallowed :stop_environment
end
end
@@ -25,7 +25,7 @@ describe EnvironmentPolicy do
let(:project) { create(:project, :public) }
it 'does not include ability to stop environment' do
- expect(policies).not_to include :stop_environment
+ expect(policy).to be_disallowed :stop_environment
end
end
@@ -38,7 +38,7 @@ describe EnvironmentPolicy do
context 'when team member has ability to stop environment' do
it 'does includes ability to stop environment' do
- expect(policies).to include :stop_environment
+ expect(policy).to be_allowed :stop_environment
end
end
@@ -49,7 +49,7 @@ describe EnvironmentPolicy do
end
it 'does not include ability to stop environment' do
- expect(policies).not_to include :stop_environment
+ expect(policy).to be_disallowed :stop_environment
end
end
end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
new file mode 100644
index 00000000000..bb0fa0c0e9c
--- /dev/null
+++ b/spec/policies/global_policy_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe GlobalPolicy, models: true do
+ let(:current_user) { create(:user) }
+ let(:user) { create(:user) }
+
+ subject { GlobalPolicy.new(current_user, [user]) }
+
+ describe "reading the list of users" do
+ context "for a logged in user" do
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+
+ context "for an anonymous user" do
+ let(:current_user) { nil }
+
+ context "when the public level is restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it { is_expected.not_to be_allowed(:read_users_list) }
+ end
+
+ context "when the public level is not restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [])
+ end
+
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+ end
+ end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index a8331ceb5ff..06db0ea56e3 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -36,16 +36,24 @@ describe GroupPolicy, models: true do
group.add_owner(owner)
end
- subject { described_class.abilities(current_user, group).to_set }
+ subject { described_class.new(current_user, group) }
+
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
context 'with no user' do
let(:current_user) { nil }
it do
- is_expected.to include(:read_group)
- is_expected.not_to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_disallowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -53,10 +61,10 @@ describe GroupPolicy, models: true do
let(:current_user) { guest }
it do
- is_expected.to include(:read_group)
- is_expected.not_to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_disallowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -64,10 +72,10 @@ describe GroupPolicy, models: true do
let(:current_user) { reporter }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -75,10 +83,10 @@ describe GroupPolicy, models: true do
let(:current_user) { developer }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -86,10 +94,10 @@ describe GroupPolicy, models: true do
let(:current_user) { master }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -97,10 +105,10 @@ describe GroupPolicy, models: true do
let(:current_user) { owner }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*master_permissions)
- is_expected.to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*master_permissions)
+ expect_allowed(*owner_permissions)
end
end
@@ -108,10 +116,10 @@ describe GroupPolicy, models: true do
let(:current_user) { admin }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*master_permissions)
- is_expected.to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*master_permissions)
+ expect_allowed(*owner_permissions)
end
end
@@ -130,16 +138,16 @@ describe GroupPolicy, models: true do
nested_group.add_owner(owner)
end
- subject { described_class.abilities(current_user, nested_group).to_set }
+ subject { described_class.new(current_user, nested_group) }
context 'with no user' do
let(:current_user) { nil }
it do
- is_expected.not_to include(:read_group)
- is_expected.not_to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_disallowed(:read_group)
+ expect_disallowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -147,10 +155,10 @@ describe GroupPolicy, models: true do
let(:current_user) { guest }
it do
- is_expected.to include(:read_group)
- is_expected.not_to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_disallowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -158,10 +166,10 @@ describe GroupPolicy, models: true do
let(:current_user) { reporter }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -169,10 +177,10 @@ describe GroupPolicy, models: true do
let(:current_user) { developer }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -180,10 +188,10 @@ describe GroupPolicy, models: true do
let(:current_user) { master }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -191,10 +199,10 @@ describe GroupPolicy, models: true do
let(:current_user) { owner }
it do
- is_expected.to include(:read_group)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*master_permissions)
- is_expected.to include(*owner_permissions)
+ expect_allowed(:read_group)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*master_permissions)
+ expect_allowed(*owner_permissions)
end
end
end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 4a07c864428..c978cbd6185 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -9,7 +9,7 @@ describe IssuePolicy, models: true do
let(:reporter_from_group_link) { create(:user) }
def permissions(user, issue)
- described_class.abilities(user, issue).to_set
+ described_class.new(user, issue)
end
context 'a private project' do
@@ -30,42 +30,42 @@ describe IssuePolicy, models: true do
end
it 'does not allow non-members to read issues' do
- expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows guests to read issues' do
- expect(permissions(guest, issue)).to include(:read_issue)
- expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(guest, issue)).to be_allowed(:read_issue)
+ expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue)
- expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
- expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
it 'allows reporters to read, update, and admin issues' do
- expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporters from group links to read, update, and admin issues' do
- expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue authors to read and update their issues' do
- expect(permissions(author, issue)).to include(:read_issue, :update_issue)
- expect(permissions(author, issue)).not_to include(:admin_issue)
+ expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(author, issue)).to be_disallowed(:admin_issue)
- expect(permissions(author, issue_no_assignee)).to include(:read_issue)
- expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
it 'allows issue assignees to read and update their issues' do
- expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
- expect(permissions(assignee, issue)).not_to include(:admin_issue)
+ expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(assignee, issue)).to be_disallowed(:admin_issue)
- expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
- expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
context 'with confidential issues' do
@@ -73,37 +73,37 @@ describe IssuePolicy, models: true do
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
it 'does not allow non-members to read confidential issues' do
- expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'does not allow guests to read confidential issues' do
- expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporters to read, update, and admin confidential issues' do
- expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporters from group links to read, update, and admin confidential issues' do
- expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue authors to read and update their confidential issues' do
- expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
- expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+ expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue)
- expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue assignees to read and update their confidential issues' do
- expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
- expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+ expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
- expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
end
end
@@ -123,37 +123,37 @@ describe IssuePolicy, models: true do
end
it 'allows guests to read issues' do
- expect(permissions(guest, issue)).to include(:read_issue)
- expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(guest, issue)).to be_allowed(:read_issue)
+ expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue)
- expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
- expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
it 'allows reporters to read, update, and admin issues' do
- expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporters from group links to read, update, and admin issues' do
- expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue authors to read and update their issues' do
- expect(permissions(author, issue)).to include(:read_issue, :update_issue)
- expect(permissions(author, issue)).not_to include(:admin_issue)
+ expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(author, issue)).to be_disallowed(:admin_issue)
- expect(permissions(author, issue_no_assignee)).to include(:read_issue)
- expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
it 'allows issue assignees to read and update their issues' do
- expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
- expect(permissions(assignee, issue)).not_to include(:admin_issue)
+ expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(assignee, issue)).to be_disallowed(:admin_issue)
- expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
- expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue)
+ expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
end
context 'with confidential issues' do
@@ -161,32 +161,32 @@ describe IssuePolicy, models: true do
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
it 'does not allow guests to read confidential issues' do
- expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporters to read, update, and admin confidential issues' do
- expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows reporter from group links to read, update, and admin confidential issues' do
- expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
- expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue authors to read and update their confidential issues' do
- expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
- expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+ expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue)
- expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
it 'allows issue assignees to read and update their confidential issues' do
- expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
- expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+ expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+ expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
- expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
end
end
end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 58aa1145c9e..4d6350fc653 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -14,7 +14,7 @@ describe PersonalSnippetPolicy, models: true do
end
def permissions(user)
- described_class.abilities(user, snippet).to_set
+ described_class.new(user, snippet)
end
context 'public snippet' do
@@ -24,9 +24,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(nil) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -34,9 +34,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(regular_user) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -44,9 +44,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(snippet.author) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.to include(:comment_personal_snippet)
- is_expected.to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(*author_permissions)
end
end
end
@@ -58,9 +58,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(nil) }
it do
- is_expected.not_to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_disallowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -68,9 +68,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(regular_user) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -78,9 +78,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(external_user) }
it do
- is_expected.not_to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_disallowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -88,9 +88,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(snippet.author) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.to include(:comment_personal_snippet)
- is_expected.to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(*author_permissions)
end
end
end
@@ -102,9 +102,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(nil) }
it do
- is_expected.not_to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_disallowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -112,9 +112,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(regular_user) }
it do
- is_expected.not_to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_disallowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -122,9 +122,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(external_user) }
it do
- is_expected.not_to include(:read_personal_snippet)
- is_expected.not_to include(:comment_personal_snippet)
- is_expected.not_to include(*author_permissions)
+ is_expected.to be_disallowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(*author_permissions)
end
end
@@ -132,9 +132,9 @@ describe PersonalSnippetPolicy, models: true do
subject { permissions(snippet.author) }
it do
- is_expected.to include(:read_personal_snippet)
- is_expected.to include(:comment_personal_snippet)
- is_expected.to include(*author_permissions)
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(*author_permissions)
end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index d70e15f006b..ca435dd0218 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -73,37 +73,45 @@ describe ProjectPolicy, models: true do
project.team << [reporter, :reporter]
end
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
+
it 'does not include the read_issue permission when the issue author is not a member of the private project' do
project = create(:empty_project, :private)
issue = create(:issue, project: project)
user = issue.author
- expect(project.team.member?(issue.author)).to eq(false)
+ expect(project.team.member?(issue.author)).to be false
- expect(BasePolicy.class_for(project).abilities(user, project).can_set)
- .not_to include(:read_issue)
-
- expect(Ability.allowed?(user, :read_issue, project)).to be_falsy
+ expect(Ability).not_to be_allowed(user, :read_issue, project)
end
- it 'does not include the wiki permissions when the feature is disabled' do
- project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
- wiki_permissions = [:read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code]
+ context 'when the feature is disabled' do
+ subject { described_class.new(owner, project) }
- permissions = described_class.abilities(owner, project).to_set
+ before do
+ project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+ end
- expect(permissions).not_to include(*wiki_permissions)
+ it 'does not include the wiki permissions' do
+ expect_disallowed :read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code
+ end
end
context 'abilities for non-public projects' do
let(:project) { create(:empty_project, namespace: owner.namespace) }
- subject { described_class.abilities(current_user, project).to_set }
+ subject { described_class.new(current_user, project) }
context 'with no user' do
let(:current_user) { nil }
- it { is_expected.to be_empty }
+ it { is_expected.to be_banned }
end
context 'guests' do
@@ -114,18 +122,18 @@ describe ProjectPolicy, models: true do
end
it do
- is_expected.to include(*guest_permissions)
- is_expected.not_to include(*reporter_public_build_permissions)
- is_expected.not_to include(*team_member_reporter_permissions)
- is_expected.not_to include(*developer_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_disallowed(*reporter_public_build_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
context 'public builds enabled' do
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(:read_build, :read_pipeline)
+ expect_allowed(*guest_permissions)
+ expect_allowed(:read_build, :read_pipeline)
end
end
@@ -135,8 +143,8 @@ describe ProjectPolicy, models: true do
end
it do
- is_expected.to include(*guest_permissions)
- is_expected.not_to include(:read_build, :read_pipeline)
+ expect_allowed(*guest_permissions)
+ expect_disallowed(:read_build, :read_pipeline)
end
end
@@ -147,8 +155,8 @@ describe ProjectPolicy, models: true do
end
it do
- is_expected.not_to include(:read_build)
- is_expected.to include(:read_pipeline)
+ expect_disallowed(:read_build)
+ expect_allowed(:read_pipeline)
end
end
end
@@ -157,12 +165,13 @@ describe ProjectPolicy, models: true do
let(:current_user) { reporter }
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*team_member_reporter_permissions)
- is_expected.not_to include(*developer_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -170,12 +179,12 @@ describe ProjectPolicy, models: true do
let(:current_user) { dev }
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*team_member_reporter_permissions)
- is_expected.to include(*developer_permissions)
- is_expected.not_to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_disallowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -183,12 +192,12 @@ describe ProjectPolicy, models: true do
let(:current_user) { master }
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*team_member_reporter_permissions)
- is_expected.to include(*developer_permissions)
- is_expected.to include(*master_permissions)
- is_expected.not_to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*master_permissions)
+ expect_disallowed(*owner_permissions)
end
end
@@ -196,12 +205,12 @@ describe ProjectPolicy, models: true do
let(:current_user) { owner }
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(*reporter_permissions)
- is_expected.to include(*team_member_reporter_permissions)
- is_expected.to include(*developer_permissions)
- is_expected.to include(*master_permissions)
- is_expected.to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*master_permissions)
+ expect_allowed(*owner_permissions)
end
end
@@ -209,12 +218,12 @@ describe ProjectPolicy, models: true do
let(:current_user) { admin }
it do
- is_expected.to include(*guest_permissions)
- is_expected.to include(*reporter_permissions)
- is_expected.not_to include(*team_member_reporter_permissions)
- is_expected.to include(*developer_permissions)
- is_expected.to include(*master_permissions)
- is_expected.to include(*owner_permissions)
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*master_permissions)
+ expect_allowed(*owner_permissions)
end
end
end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index d2b2528c57a..2799f03fb9b 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -15,7 +15,15 @@ describe ProjectSnippetPolicy, models: true do
def abilities(user, snippet_visibility)
snippet = create(:project_snippet, snippet_visibility, project: project)
- described_class.abilities(user, snippet).to_set
+ described_class.new(user, snippet)
+ end
+
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
end
context 'public snippet' do
@@ -23,8 +31,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(nil, :public) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -32,8 +40,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(regular_user, :public) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -41,8 +49,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(external_user, :public) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
end
@@ -52,8 +60,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(nil, :internal) }
it do
- is_expected.not_to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_disallowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -61,8 +69,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(regular_user, :internal) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -70,8 +78,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(external_user, :internal) }
it do
- is_expected.not_to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_disallowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -83,8 +91,8 @@ describe ProjectSnippetPolicy, models: true do
end
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
end
@@ -94,8 +102,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(nil, :private) }
it do
- is_expected.not_to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_disallowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -103,19 +111,19 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(regular_user, :private) }
it do
- is_expected.not_to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_disallowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
context 'snippet author' do
let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
- subject { described_class.abilities(regular_user, snippet).to_set }
+ subject { described_class.new(regular_user, snippet) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_allowed(*author_permissions)
end
end
@@ -127,8 +135,8 @@ describe ProjectSnippetPolicy, models: true do
end
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -140,8 +148,8 @@ describe ProjectSnippetPolicy, models: true do
end
it do
- is_expected.to include(:read_project_snippet)
- is_expected.not_to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_disallowed(*author_permissions)
end
end
@@ -149,8 +157,8 @@ describe ProjectSnippetPolicy, models: true do
subject { abilities(create(:admin), :private) }
it do
- is_expected.to include(:read_project_snippet)
- is_expected.to include(*author_permissions)
+ expect_allowed(:read_project_snippet)
+ expect_allowed(*author_permissions)
end
end
end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index d5761390d39..0251d5dcf1c 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -4,34 +4,34 @@ describe UserPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
- subject { described_class.abilities(current_user, user).to_set }
+ subject { UserPolicy.new(current_user, user) }
describe "reading a user's information" do
- it { is_expected.to include(:read_user) }
+ it { is_expected.to be_allowed(:read_user) }
end
describe "destroying a user" do
context "when a regular user tries to destroy another regular user" do
- it { is_expected.not_to include(:destroy_user) }
+ it { is_expected.not_to be_allowed(:destroy_user) }
end
context "when a regular user tries to destroy themselves" do
let(:current_user) { user }
- it { is_expected.to include(:destroy_user) }
+ it { is_expected.to be_allowed(:destroy_user) }
end
context "when an admin user tries to destroy a regular user" do
let(:current_user) { create(:user, :admin) }
- it { is_expected.to include(:destroy_user) }
+ it { is_expected.to be_allowed(:destroy_user) }
end
context "when an admin user tries to destroy a ghost user" do
let(:current_user) { create(:user, :admin) }
let(:user) { create(:user, :ghost) }
- it { is_expected.not_to include(:destroy_user) }
+ it { is_expected.not_to be_allowed(:destroy_user) }
end
end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 518e97d17a1..f05d5c7fce5 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -85,7 +85,7 @@ describe Ci::BuildPresenter do
describe 'quack like a Ci::Build permission-wise' do
context 'user is not allowed' do
- let(:project) { build_stubbed(:empty_project, public_builds: false) }
+ let(:project) { create(:empty_project, public_builds: false) }
it 'returns false' do
expect(presenter.can?(nil, :read_build)).to be_falsy
@@ -93,7 +93,7 @@ describe Ci::BuildPresenter do
end
context 'user is allowed' do
- let(:project) { build_stubbed(:empty_project, :public) }
+ let(:project) { create(:empty_project, :public) }
it 'returns true' do
expect(presenter.can?(nil, :read_build)).to be_truthy
diff --git a/spec/presenters/ci/group_variable_presenter_spec.rb b/spec/presenters/ci/group_variable_presenter_spec.rb
new file mode 100644
index 00000000000..d404028405b
--- /dev/null
+++ b/spec/presenters/ci/group_variable_presenter_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Ci::GroupVariablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:group) { create(:group) }
+ let(:variable) { create(:ci_group_variable, group: group) }
+
+ subject(:presenter) do
+ described_class.new(variable)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+
+ describe '#initialize' do
+ it 'takes a variable and optional params' do
+ expect { presenter }.not_to raise_error
+ end
+
+ it 'exposes variable' do
+ expect(presenter.variable).to eq(variable)
+ end
+
+ it 'forwards missing methods to variable' do
+ expect(presenter.key).to eq(variable.key)
+ end
+ end
+
+ describe '#placeholder' do
+ subject { described_class.new(variable).placeholder }
+
+ it { is_expected.to eq('GROUP_VARIABLE') }
+ end
+
+ describe '#form_path' do
+ context 'when variable is persisted' do
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+
+ context 'when variable is not persisted' do
+ let(:variable) { build(:ci_group_variable, group: group) }
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(group_variables_path(group)) }
+ end
+ end
+
+ describe '#edit_path' do
+ subject { described_class.new(variable).edit_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+
+ describe '#delete_path' do
+ subject { described_class.new(variable).delete_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+end
diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb
new file mode 100644
index 00000000000..9e6aae7bcad
--- /dev/null
+++ b/spec/presenters/ci/variable_presenter_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Ci::VariablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:project) { create(:empty_project) }
+ let(:variable) { create(:ci_variable, project: project) }
+
+ subject(:presenter) do
+ described_class.new(variable)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+
+ describe '#initialize' do
+ it 'takes a variable and optional params' do
+ expect { presenter }.not_to raise_error
+ end
+
+ it 'exposes variable' do
+ expect(presenter.variable).to eq(variable)
+ end
+
+ it 'forwards missing methods to variable' do
+ expect(presenter.key).to eq(variable.key)
+ end
+ end
+
+ describe '#placeholder' do
+ subject { described_class.new(variable).placeholder }
+
+ it { is_expected.to eq('PROJECT_VARIABLE') }
+ end
+
+ describe '#form_path' do
+ context 'when variable is persisted' do
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+
+ context 'when variable is not persisted' do
+ let(:variable) { build(:ci_variable, project: project) }
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(project_variables_path(project)) }
+ end
+ end
+
+ describe '#edit_path' do
+ subject { described_class.new(variable).edit_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+
+ describe '#delete_path' do
+ subject { described_class.new(variable).delete_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index cdb60fc0d1a..8b62aa268d9 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -237,6 +237,28 @@ describe API::CommitStatuses do
end
end
+ context 'when retrying a commit status' do
+ before do
+ post api(post_url, developer),
+ { state: 'failed', name: 'test', ref: 'master' }
+
+ post api(post_url, developer),
+ { state: 'success', name: 'test', ref: 'master' }
+ end
+
+ it 'correctly posts a new commit status' do
+ expect(response).to have_http_status(201)
+ expect(json_response['sha']).to eq(commit.id)
+ expect(json_response['status']).to eq('success')
+ end
+
+ it 'retries a commit status' do
+ expect(CommitStatus.count).to eq 2
+ expect(CommitStatus.first).to be_retried
+ expect(CommitStatus.last.pipeline).to be_success
+ end
+ end
+
context 'when status is invalid' do
before do
post api(post_url, developer), state: 'invalid'
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index f169e6661d1..7e21006b254 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -4,6 +4,13 @@ describe API::Features do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
+ before do
+ Flipper.unregister_groups
+ Flipper.register(:perf_team) do |actor|
+ actor.respond_to?(:admin) && actor.admin?
+ end
+ end
+
describe 'GET /features' do
let(:expected_features) do
[
@@ -16,6 +23,14 @@ describe API::Features do
'name' => 'feature_2',
'state' => 'off',
'gates' => [{ 'key' => 'boolean', 'value' => false }]
+ },
+ {
+ 'name' => 'feature_3',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'groups', 'value' => ['perf_team'] }
+ ]
}
]
end
@@ -23,6 +38,7 @@ describe API::Features do
before do
Feature.get('feature_1').enable
Feature.get('feature_2').disable
+ Feature.get('feature_3').enable Feature.group(:perf_team)
end
it 'returns a 401 for anonymous users' do
@@ -47,30 +63,84 @@ describe API::Features do
describe 'POST /feature' do
let(:feature_name) { 'my_feature' }
- it 'returns a 401 for anonymous users' do
- post api("/features/#{feature_name}")
- expect(response).to have_http_status(401)
- end
+ context 'when the feature does not exist' do
+ it 'returns a 401 for anonymous users' do
+ post api("/features/#{feature_name}")
- it 'returns a 403 for users' do
- post api("/features/#{feature_name}", user)
+ expect(response).to have_http_status(401)
+ end
- expect(response).to have_http_status(403)
- end
+ it 'returns a 403 for users' do
+ post api("/features/#{feature_name}", user)
- it 'creates an enabled feature if passed true' do
- post api("/features/#{feature_name}", admin), value: 'true'
+ expect(response).to have_http_status(403)
+ end
- expect(response).to have_http_status(201)
- expect(Feature.get(feature_name)).to be_enabled
- end
+ context 'when passed value=true' do
+ it 'creates an enabled feature' do
+ post api("/features/#{feature_name}", admin), value: 'true'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'on',
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }])
+ end
+
+ it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
+ post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'groups', 'value' => ['perf_team'] }
+ ])
+ end
+
+ it 'creates an enabled feature for the given user when passed user=username' do
+ post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+ ])
+ end
+
+ it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do
+ post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'groups', 'value' => ['perf_team'] },
+ { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+ ])
+ end
+ end
- it 'creates a feature with the given percentage if passed an integer' do
- post api("/features/#{feature_name}", admin), value: '50'
+ it 'creates a feature with the given percentage if passed an integer' do
+ post api("/features/#{feature_name}", admin), value: '50'
- expect(response).to have_http_status(201)
- expect(Feature.get(feature_name).percentage_of_time_value).to be(50)
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'percentage_of_time', 'value' => 50 }
+ ])
+ end
end
context 'when the feature exists' do
@@ -80,11 +150,83 @@ describe API::Features do
feature.disable # This also persists the feature on the DB
end
- it 'enables the feature if passed true' do
- post api("/features/#{feature_name}", admin), value: 'true'
+ context 'when passed value=true' do
+ it 'enables the feature' do
+ post api("/features/#{feature_name}", admin), value: 'true'
- expect(response).to have_http_status(201)
- expect(feature).to be_enabled
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'on',
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }])
+ end
+
+ it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
+ post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'groups', 'value' => ['perf_team'] }
+ ])
+ end
+
+ it 'enables the feature for the given user when passed user=username' do
+ post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+ ])
+ end
+ end
+
+ context 'when feature is enabled and value=false is passed' do
+ it 'disables the feature' do
+ feature.enable
+ expect(feature).to be_enabled
+
+ post api("/features/#{feature_name}", admin), value: 'false'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'off',
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ end
+
+ it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do
+ feature.enable(Feature.group(:perf_team))
+ expect(Feature.get(feature_name).enabled?(admin)).to be_truthy
+
+ post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'off',
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ end
+
+ it 'disables the feature for the given user when passed user=username' do
+ feature.enable(user)
+ expect(Feature.get(feature_name).enabled?(user)).to be_truthy
+
+ post api("/features/#{feature_name}", admin), value: 'false', user: user.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'off',
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ end
end
context 'with a pre-existing percentage value' do
@@ -96,7 +238,13 @@ describe API::Features do
post api("/features/#{feature_name}", admin), value: '30'
expect(response).to have_http_status(201)
- expect(Feature.get(feature_name).percentage_of_time_value).to be(30)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'percentage_of_time', 'value' => 30 }
+ ])
end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 191c60aba31..25ec44fa036 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -14,6 +14,10 @@ describe API::Helpers do
let(:request) { Rack::Request.new(env) }
let(:header) { }
+ before do
+ allow_any_instance_of(self.class).to receive(:options).and_return({})
+ end
+
def set_env(user_or_token, identifier)
clear_env
clear_param
@@ -167,7 +171,6 @@ describe API::Helpers do
it "returns nil for a token without the appropriate scope" do
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
- allow_access_with_scope('write_user')
expect(current_user).to be_nil
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6deaea956e0..cde4fa888a0 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -220,26 +220,72 @@ describe API::Internal do
end
context "git pull" do
- it do
- pull(key, project)
+ context "gitaly disabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(false)
+ pull(key, project)
- expect(response).to have_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(user).to have_an_activity_record
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).to be_nil
+ expect(user).to have_an_activity_record
+ end
+ end
+
+ context "gitaly enabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(true)
+ pull(key, project)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).to have_an_activity_record
+ end
end
end
context "git push" do
- it do
- push(key, project)
+ context "gitaly disabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(false)
+ push(key, project)
- expect(response).to have_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(user).not_to have_an_activity_record
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).to be_nil
+ expect(user).not_to have_an_activity_record
+ end
+ end
+
+ context "gitaly enabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(true)
+ push(key, project)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).not_to have_an_activity_record
+ end
end
context 'project as /namespace/project' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 79cac721202..9837fedb522 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -772,7 +772,7 @@ describe API::Issues do
end
end
- context 'CE restrictions' do
+ context 'single assignee restrictions' do
it 'creates a new project issue with no more than one assignee' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', assignee_ids: [user2.id, guest.id]
@@ -1123,7 +1123,7 @@ describe API::Issues do
expect(json_response['assignees'].first['name']).to eq(user2.name)
end
- context 'CE restrictions' do
+ context 'single assignee restrictions' do
it 'updates an issue with several assignees but only one has been applied' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
assignee_ids: [user2.id, guest.id]
@@ -1462,6 +1462,25 @@ describe API::Issues do
end
end
+ describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
def expect_paginated_array_response(size: nil)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4d0bd67c571..360a82196a8 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -16,7 +16,11 @@ describe API::MergeRequests do
let!(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
end
+ let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
let!(:label_link) { create(:label_link, label: label, target: merge_request) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request) }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
before do
project.team << [user, :reporter]
@@ -32,6 +36,18 @@ describe API::MergeRequests do
end
context "when authenticated" do
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.count
+
+ create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
+
+ expect do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.not_to exceed_query_limit(control_count)
+ end
+
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
@@ -44,6 +60,9 @@ describe API::MergeRequests do
expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
expect(json_response.last['merge_commit_sha']).to be_nil
expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.last['downvotes']).to eq(1)
+ expect(json_response.last['upvotes']).to eq(1)
+ expect(json_response.last['labels']).to eq([label2.title, label.title])
expect(json_response.first['title']).to eq(merge_request_merged.title)
expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
expect(json_response.first['merge_commit_sha']).not_to be_nil
@@ -145,7 +164,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
end
it 'returns an array of labeled merge requests where all labels match' do
@@ -236,8 +255,8 @@ describe API::MergeRequests do
expect(json_response['author']).to be_a Hash
expect(json_response['target_branch']).to eq(merge_request.target_branch)
expect(json_response['source_branch']).to eq(merge_request.source_branch)
- expect(json_response['upvotes']).to eq(0)
- expect(json_response['downvotes']).to eq(0)
+ expect(json_response['upvotes']).to eq(1)
+ expect(json_response['downvotes']).to eq(1)
expect(json_response['source_project_id']).to eq(merge_request.source_project.id)
expect(json_response['target_project_id']).to eq(merge_request.target_project.id)
expect(json_response['work_in_progress']).to be_falsy
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 3bf16a3ae27..26cf653ca8e 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -15,6 +15,20 @@ describe API::Namespaces do
end
context "when authenticated as admin" do
+ it "returns correct attributes" do
+ get api("/namespaces", admin)
+
+ group_kind_json_response = json_response.find { |resource| resource['kind'] == 'group' }
+ user_kind_json_response = json_response.find { |resource| resource['kind'] == 'user' }
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(group_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path',
+ 'parent_id', 'members_count_with_descendants')
+
+ expect(user_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
+ end
+
it "admin: returns an array of all namespaces" do
get api("/namespaces", admin)
@@ -37,6 +51,27 @@ describe API::Namespaces do
end
context "when authenticated as a regular user" do
+ it "returns correct attributes when user can admin group" do
+ group1.add_owner(user)
+
+ get api("/namespaces", user)
+
+ owned_group_response = json_response.find { |resource| resource['id'] == group1.id }
+
+ expect(owned_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path',
+ 'parent_id', 'members_count_with_descendants')
+ end
+
+ it "returns correct attributes when user cannot admin group" do
+ group1.add_guest(user)
+
+ get api("/namespaces", user)
+
+ guest_group_response = json_response.find { |resource| resource['id'] == group1.id }
+
+ expect(guest_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
+ end
+
it "user: returns an array of namespaces" do
get api("/namespaces", user)
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 85d11deb26f..b34555d2815 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -279,6 +279,8 @@ describe API::PipelineSchedules do
end
context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master) }
+
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 518639f45a2..f220972bae3 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -5,6 +5,26 @@ describe API::ProjectSnippets do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
+ describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
describe 'GET /projects/:project_id/snippets/' do
let(:user) { create(:user) }
@@ -20,7 +40,7 @@ describe API::ProjectSnippets do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
- expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ expect(json_response.map { |snippet| snippet['id'] }).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
end
@@ -38,7 +58,7 @@ describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id' do
let(:user) { create(:user) }
- let(:snippet) { create(:project_snippet, :public, project: project) }
+ let(:snippet) { create(:project_snippet, :public, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fd7ff0b9cff..fa704f23857 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -52,6 +52,24 @@ describe API::Projects do
end
end
+ shared_examples_for 'projects response without N + 1 queries' do
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api('/projects', current_user)
+ end.count
+
+ if defined?(additional_project)
+ additional_project
+ else
+ create(:empty_project, :public)
+ end
+
+ expect do
+ get api('/projects', current_user)
+ end.not_to exceed_query_limit(control_count + 8)
+ end
+ end
+
let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
before do
project
@@ -62,9 +80,13 @@ describe API::Projects do
context 'when unauthenticated' do
it_behaves_like 'projects response' do
- let(:filter) { {} }
+ let(:filter) { { search: project.name } }
+ let(:current_user) { user }
+ let(:projects) { [project] }
+ end
+
+ it_behaves_like 'projects response without N + 1 queries' do
let(:current_user) { nil }
- let(:projects) { [public_project] }
end
end
@@ -75,6 +97,21 @@ describe API::Projects do
let(:projects) { [public_project, project, project2, project3] }
end
+ it_behaves_like 'projects response without N + 1 queries' do
+ let(:current_user) { user }
+ end
+
+ context 'when some projects are in a group' do
+ before do
+ create(:empty_project, :public, group: create(:group))
+ end
+
+ it_behaves_like 'projects response without N + 1 queries' do
+ let(:current_user) { user }
+ let(:additional_project) { create(:empty_project, :public, group: create(:group)) }
+ end
+ end
+
it 'includes the project labels as the tag_list' do
get api('/projects', user)
@@ -347,7 +384,8 @@ describe API::Projects do
wiki_enabled: false,
only_allow_merge_if_pipeline_succeeds: false,
request_access_enabled: true,
- only_allow_merge_if_all_discussions_are_resolved: false
+ only_allow_merge_if_all_discussions_are_resolved: false,
+ ci_config_path: 'a/custom/path'
})
post api('/projects', user), project
@@ -475,6 +513,26 @@ describe API::Projects do
end
end
+ describe 'GET /users/:user_id/projects/' do
+ let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
+
+ it 'returns error when user not found' do
+ get api('/users/9999/projects/')
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns projects filtered by user' do
+ get api("/users/#{user4.id}/projects/", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
+ end
+ end
+
describe 'POST /projects/user/:id' do
before do
expect(project).to be_persisted
@@ -653,6 +711,7 @@ describe API::Projects do
expect(json_response['star_count']).to be_present
expect(json_response['forks_count']).to be_present
expect(json_response['public_jobs']).to be_present
+ expect(json_response['ci_config_path']).to be_nil
expect(json_response['shared_with_groups']).to be_an Array
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
@@ -698,7 +757,8 @@ describe API::Projects do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
- 'full_path' => user.namespace.full_path
+ 'full_path' => user.namespace.full_path,
+ 'parent_id' => nil
})
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 339a57a1f20..ca5d98c78ef 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -351,7 +351,8 @@ describe API::Runner do
let(:expected_cache) do
[{ 'key' => 'cache_key',
'untracked' => false,
- 'paths' => ['vendor/*'] }]
+ 'paths' => ['vendor/*'],
+ 'policy' => 'pull-push' }]
end
it 'picks a job' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index b20a187acfe..373fab4d98a 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -271,4 +271,25 @@ describe API::Snippets do
expect(json_response['message']).to eq('404 Snippet Not Found')
end
end
+
+ describe "GET /snippets/:id/user_agent_detail" do
+ let(:admin) { create(:admin) }
+ let(:snippet) { create(:personal_snippet, :public, author: user) }
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
+
+ it 'exposes known attributes' do
+ get api("/snippets/#{snippet.id}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/snippets/#{snippet.id}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index c0174b304c8..c34b88f0741 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do
context "when unauthenticated" do
- it "returns authentication error" do
+ it "returns authorization error when the `username` parameter is not passed" do
get api("/users")
- expect(response).to have_http_status(401)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it "returns the user when a valid `username` parameter is passed" do
+ user = create(:user)
+
+ get api("/users"), username: user.username
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['id']).to eq(user.id)
+ expect(json_response[0]['username']).to eq(user.username)
+ end
+
+ it "returns authorization error when the `username` parameter refers to an inaccessible user" do
+ user = create(:user)
+
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+
+ get api("/users"), username: user.username
+
+ expect(response).to have_http_status(403)
+ end
+
+ it "returns an empty response when an invalid `username` parameter is passed" do
+ get api("/users"), username: 'invalid'
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(0)
end
end
@@ -132,12 +163,42 @@ describe API::Users do
expect(response).to have_http_status(400)
end
+
+ it "returns a user created before a specific date" do
+ user = create(:user, created_at: Date.new(2000, 1, 1))
+
+ get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['username']).to eq(user.username)
+ end
+
+ it "returns no users created before a specific date" do
+ create(:user, created_at: Date.new(2001, 1, 1))
+
+ get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(0)
+ end
+
+ it "returns users created before and after a specific date" do
+ user = create(:user, created_at: Date.new(2001, 1, 1))
+
+ get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['username']).to eq(user.username)
+ end
end
end
describe "GET /users/:id" do
it "returns a user by id" do
get api("/users/#{user.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
@@ -148,9 +209,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil
end
- it "returns a 401 if unauthenticated" do
- get api("/users/9998")
- expect(response).to have_http_status(401)
+ context 'for an anonymous user' do
+ it "returns a user by id" do
+ get api("/users/#{user.id}")
+
+ expect(response).to have_http_status(200)
+ expect(json_response['username']).to eq(user.username)
+ end
+
+ it "returns a 404 if the target user is present but inaccessible" do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
+
+ get api("/users/#{user.id}")
+
+ expect(response).to have_http_status(404)
+ end
end
it "returns a 404 error if user id not found" do
@@ -345,6 +419,14 @@ describe API::Users do
expect(json_response['identities'].first['provider']).to eq('github')
end
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { '/users' }
+ let(:api_call) { method(:api) }
+
+ include_examples 'does not allow the "read_user" scope'
+ end
end
describe "GET /users/sign_up" do
@@ -842,6 +924,13 @@ describe API::Users do
expect(response).to match_response_schema('public_api/v4/user/public')
expect(json_response['id']).to eq(user.id)
end
+
+ context "scopes" do
+ let(:path) { "/user" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
context 'with admin' do
@@ -911,6 +1000,13 @@ describe API::Users do
expect(json_response).to be_an Array
expect(json_response.first["title"]).to eq(key.title)
end
+
+ context "scopes" do
+ let(:path) { "/user/keys" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
end
@@ -944,6 +1040,13 @@ describe API::Users do
expect(response).to have_http_status(404)
end
+
+ context "scopes" do
+ let(:path) { "/user/keys/#{key.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe "POST /user/keys" do
@@ -1033,6 +1136,13 @@ describe API::Users do
expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email)
end
+
+ context "scopes" do
+ let(:path) { "/user/emails" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
end
@@ -1065,6 +1175,13 @@ describe API::Users do
expect(response).to have_http_status(404)
end
+
+ context "scopes" do
+ let(:path) { "/user/emails/#{email.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe "POST /user/emails" do
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index cb74868324c..af44ffa2331 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -734,7 +734,8 @@ describe API::V3::Projects do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
- 'full_path' => user.namespace.full_path
+ 'full_path' => user.namespace.full_path,
+ 'parent_id' => nil
})
end
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
index 6d7401f9764..de7499a4e43 100644
--- a/spec/requests/api/v3/users_spec.rb
+++ b/spec/requests/api/v3/users_spec.rb
@@ -67,6 +67,19 @@ describe API::V3::Users do
expect(json_response.first['title']).to eq(key.title)
end
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { "/users/#{user.id}/keys" }
+ let(:api_call) { method(:v3_api) }
+
+ before do
+ user.keys << key
+ user.save
+ end
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe 'GET /user/:id/emails' do
@@ -287,7 +300,7 @@ describe API::V3::Users do
end
it 'returns a 404 error if not found' do
- get v3_api('/users/42/events', user)
+ get v3_api('/users/420/events', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -312,5 +325,13 @@ describe API::V3::Users do
expect(json_response['is_admin']).to be_nil
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { '/users' }
+ let(:api_call) { method(:v3_api) }
+
+ include_examples 'does not allow the "read_user" scope'
+ end
end
end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index d4d3c9478a0..e78d2cfdb33 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -21,7 +21,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the issue events' do
- get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_issue_path(project, format: :json)
first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
@@ -30,7 +30,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the plan events' do
- get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_plan_path(project, format: :json)
first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id
@@ -39,7 +39,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the code events' do
- get namespace_project_cycle_analytics_code_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_code_path(project, format: :json)
expect(json_response['events']).not_to be_empty
@@ -49,14 +49,14 @@ describe 'cycle analytics events', api: true do
end
it 'lists the test events' do
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_test_path(project, format: :json)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
end
it 'lists the review events' do
- get namespace_project_cycle_analytics_review_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_review_path(project, format: :json)
first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
@@ -65,14 +65,14 @@ describe 'cycle analytics events', api: true do
end
it 'lists the staging events' do
- get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_staging_path(project, format: :json)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
end
it 'lists the production events' do
- get namespace_project_cycle_analytics_production_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_production_path(project, format: :json)
first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
@@ -84,7 +84,7 @@ describe 'cycle analytics events', api: true do
it 'lists the test events' do
branch = project.merge_requests.first.source_branch
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch)
+ get project_cycle_analytics_test_path(project, format: :json, branch: branch)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
@@ -97,19 +97,19 @@ describe 'cycle analytics events', api: true do
end
it 'does not list the test events' do
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_test_path(project, format: :json)
expect(response).to have_http_status(:not_found)
end
it 'does not list the staging events' do
- get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_staging_path(project, format: :json)
expect(response).to have_http_status(:not_found)
end
it 'lists the issue events' do
- get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_issue_path(project, format: :json)
expect(response).to have_http_status(:ok)
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 95d40138fea..2f1c3c95e59 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -246,28 +246,13 @@ describe 'project routing' do
end
end
- # diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
- # commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits
- # merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge
- # ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status
- # toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription
- # branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
- # branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
- # update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches
- # namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index
- # POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create
- # new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new
- # edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
- # namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show
- # PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
- # PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
describe Projects::MergeRequestsController, 'routing' do
- it 'to #diffs' do
- expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ it 'to #commits' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/commits.json')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
end
- it 'to #commits' do
- expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ it 'to #pipelines' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines.json')).to route_to('projects/merge_requests#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
end
it 'to #merge' do
@@ -277,25 +262,59 @@ describe 'project routing' do
)
end
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff')
+ expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch')
+ expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'diffs')
+ expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'commits')
+ expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'pipelines')
+ end
+
+ it_behaves_like 'RESTful project resources' do
+ let(:controller) { 'merge_requests' }
+ let(:actions) { [:index, :edit, :show, :update] }
+ end
+ end
+
+ describe Projects::MergeRequests::CreationsController, 'routing' do
+ it 'to #new' do
+ expect(get('/gitlab/gitlabhq/merge_requests/new')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/merge_requests/new/diffs')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'diffs')
+ expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'pipelines')
+ end
+
+ it 'to #create' do
+ expect(post('/gitlab/gitlabhq/merge_requests')).to route_to('projects/merge_requests/creations#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+
it 'to #branch_from' do
- expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/merge_requests/new/branch_from')).to route_to('projects/merge_requests/creations#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #branch_to' do
- expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/merge_requests/new/branch_to')).to route_to('projects/merge_requests/creations#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it 'to #show' do
- expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff')
- expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch')
+ it 'to #pipelines' do
+ expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines.json')).to route_to('projects/merge_requests/creations#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json')
end
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'merge_requests' }
- let(:actions) { [:index, :create, :new, :edit, :show, :update] }
+ it 'to #diffs' do
+ expect(get('/gitlab/gitlabhq/merge_requests/new/diffs.json')).to route_to('projects/merge_requests/creations#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json')
+ end
+ end
+
+ describe Projects::MergeRequests::DiffsController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/diffs.json')).to route_to('projects/merge_requests/diffs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
end
end
+ describe Projects::MergeRequests::ConflictsController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/conflicts')).to route_to('projects/merge_requests/conflicts#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+ end
# raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw
# project_snippets GET /:project_id/snippets(.:format) snippets#index
# POST /:project_id/snippets(.:format) snippets#create
diff --git a/spec/rubocop/cop/active_record_dependent_spec.rb b/spec/rubocop/cop/active_record_dependent_spec.rb
new file mode 100644
index 00000000000..599a032bfc5
--- /dev/null
+++ b/spec/rubocop/cop/active_record_dependent_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/active_record_dependent'
+
+describe RuboCop::Cop::ActiveRecordDependent do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'inside the app/models directory' do
+ it 'registers an offense when dependent: is used' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside the app/models directory' do
+ it 'does nothing' do
+ allow(cop).to receive(:in_model?).and_return(false)
+
+ inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/active_record_serialize_spec.rb
index 5bd7e5fa926..b94b25cecd0 100644
--- a/spec/rubocop/cop/activerecord_serialize_spec.rb
+++ b/spec/rubocop/cop/active_record_serialize_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/activerecord_serialize'
+require_relative '../../../rubocop/cop/active_record_serialize'
-describe RuboCop::Cop::ActiverecordSerialize do
+describe RuboCop::Cop::ActiveRecordSerialize do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/in_batches_spec.rb b/spec/rubocop/cop/in_batches_spec.rb
new file mode 100644
index 00000000000..072481984c6
--- /dev/null
+++ b/spec/rubocop/cop/in_batches_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/in_batches'
+
+describe RuboCop::Cop::InBatches do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense when in_batches is used' do
+ inspect_source(cop, 'foo.in_batches do; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
new file mode 100644
index 00000000000..bc47b45cad7
--- /dev/null
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/project_path_helper'
+
+describe RuboCop::Cop::ProjectPathHelper do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context "when using namespace_project with the project's namespace" do
+ let(:source) { 'edit_namespace_project_issue_path(@issue.project.namespace, @issue.project, @issue)' }
+ let(:correct_source) { 'edit_project_issue_path(@issue.project, @issue)' }
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['edit_namespace_project_issue_path'])
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(cop, source)
+
+ expect(autocorrected).to eq(correct_source)
+ end
+ end
+
+ context 'when using namespace_project with a different namespace' do
+ it 'registers no offense' do
+ inspect_source(cop, 'edit_namespace_project_issue_path(namespace, project)')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index ed89fccc3d0..9620f9665cf 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -29,7 +29,7 @@ describe DeployKeyEntity do
{
id: project.id,
name: project.name,
- full_path: namespace_project_path(project.namespace, project),
+ full_path: project_path(project),
full_name: project.full_name
}
]
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index 87f093ee8ce..11225fad18a 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -2,40 +2,71 @@ require 'spec_helper'
describe AccessTokenValidationService, services: true do
describe ".include_any_scope?" do
+ let(:request) { double("request") }
+
it "returns true if the required scope is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:api]
- expect(described_class.new(token).include_any_scope?([:api])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if more than one of the required scopes is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope])
+ scopes = [:api, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if the list of required scopes is an exact match for the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope])
+ scopes = [:api, :read_user, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:api, :read_user, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it 'returns true if the list of required scopes is blank' do
token = double("token", scopes: [])
+ scopes = []
- expect(described_class.new(token).include_any_scope?([])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns false if there are no scopes in common between the required scopes and the token scopes" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:other_scope]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+ end
+
+ context "conditions" do
+ it "ignores any scopes whose `if` condition returns false" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { false })]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+ end
+
+ it "does not ignore scopes whose `if` condition is not set" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { false }), :read_user]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+ end
+
+ it "does not ignore scopes whose `if` condition returns true" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { true }), API::Scope.new(:read_user, if: ->(_) { false })]
- expect(described_class.new(token).include_any_scope?([:other_scope])).to be(false)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+ end
end
end
end
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index effa4633d13..89615df1692 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -26,6 +26,8 @@ describe Boards::CreateService, services: true do
end
it 'does not create a new board' do
+ expect(service).to receive(:can_create_board?) { false }
+
expect { service.execute }.not_to change(project.boards, :count)
end
end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index a1e220c2322..a66cc2cd6e9 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -67,7 +67,7 @@ describe Boards::Issues::ListService, services: true do
issues = described_class.new(project, user, params).execute
- expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
+ expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1]
end
it 'returns opened issues that have label list applied when listing issues from a label list' do
diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb
index cae74df9c90..fe21ca0b3cb 100644
--- a/spec/services/delete_merged_branches_service_spec.rb
+++ b/spec/services/delete_merged_branches_service_spec.rb
@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do
expect(project.repository.branch_names).to include('master')
end
+ it 'keeps protected branches' do
+ create(:protected_branch, project: project, name: 'improve/awesome')
+
+ service.execute
+
+ expect(project.repository.branch_names).to include('improve/awesome')
+ end
+
context 'user without rights' do
let(:user) { create(:user) }
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index ac7ccfbaab0..213678c27f5 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -12,7 +12,6 @@ describe GitHooksService, services: true do
@oldrev = sample_commit.parent_id
@newrev = sample_commit.id
@ref = 'refs/heads/feature'
- @repo_path = project.repository.path_to_repo
end
describe '#execute' do
@@ -21,7 +20,7 @@ describe GitHooksService, services: true do
hook = double(trigger: [true, nil])
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }
+ service.execute(user, project, @blankrev, @newrev, @ref) { }
end
end
@@ -31,7 +30,7 @@ describe GitHooksService, services: true do
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
- service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+ service.execute(user, project, @blankrev, @newrev, @ref)
end.to raise_error(GitHooksService::PreReceiveError)
end
end
@@ -43,7 +42,7 @@ describe GitHooksService, services: true do
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
- service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+ service.execute(user, project, @blankrev, @newrev, @ref)
end.to raise_error(GitHooksService::PreReceiveError)
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index ca827fc0f39..8e8816870e1 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -401,18 +401,6 @@ describe GitPushService, services: true do
expect(SystemNoteService).not_to receive(:cross_reference)
execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
-
- it "doesn't close issues when external issue tracker is in use" do
- allow_any_instance_of(Project).to receive(:default_issues_tracker?)
- .and_return(false)
- external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern)
- allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker)
-
- # The push still shouldn't create cross-reference notes.
- expect do
- execute_service(project, commit_author, @oldrev, @newrev, 'refs/heads/hurf' )
- end.not_to change { Note.where(project_id: project.id, system: true).count }
- end
end
context "to non-default branches" do
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index a37257d1bf4..d59b37bee36 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -15,6 +15,14 @@ describe Groups::DestroyService, services: true do
group.add_user(user, Gitlab::Access::OWNER)
end
+ def destroy_group(group, user, async)
+ if async
+ Groups::DestroyService.new(group, user).async_execute
+ else
+ Groups::DestroyService.new(group, user).execute
+ end
+ end
+
shared_examples 'group destruction' do |async|
context 'database records' do
before do
@@ -30,30 +38,14 @@ describe Groups::DestroyService, services: true do
context 'file system' do
context 'Sidekiq inline' do
before do
- # Run sidekiq immediatly to check that renamed dir will be removed
+ # Run sidekiq immediately to check that renamed dir will be removed
Sidekiq::Testing.inline! { destroy_group(group, user, async) }
end
- it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
- it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
- end
-
- context 'Sidekiq fake' do
- before do
- # Don't run sidekiq to check if renamed repository exists
- Sidekiq::Testing.fake! { destroy_group(group, user, async) }
+ it 'verifies that paths have been deleted' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey
+ expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
end
-
- it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
- it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
- end
- end
-
- def destroy_group(group, user, async)
- if async
- Groups::DestroyService.new(group, user).async_execute
- else
- Groups::DestroyService.new(group, user).execute
end
end
end
@@ -61,6 +53,26 @@ describe Groups::DestroyService, services: true do
describe 'asynchronous delete' do
it_behaves_like 'group destruction', true
+ context 'Sidekiq fake' do
+ before do
+ # Don't run Sidekiq to verify that group and projects are not actually destroyed
+ Sidekiq::Testing.fake! { destroy_group(group, user, true) }
+ end
+
+ after do
+ # Clean up stale directories
+ gitlab_shell.rm_namespace(project.repository_storage_path, group.path)
+ gitlab_shell.rm_namespace(project.repository_storage_path, remove_path)
+ end
+
+ it 'verifies original paths and projects still exist' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_truthy
+ expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+ expect(Project.unscoped.count).to eq(1)
+ expect(Group.unscoped.count).to eq(2)
+ end
+ end
+
context 'potential race conditions' do
context "when the `GroupDestroyWorker` task runs immediately" do
it "deletes the group" do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index d1dd1466d95..36d5038fb95 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -37,9 +37,6 @@ describe Issues::MoveService, services: true do
describe '#execute' do
shared_context 'issue move executed' do
- let!(:milestone2) do
- create(:milestone, project_id: new_project.id, title: 'v9.0')
- end
let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }
let!(:new_issue) { move_service.execute(old_issue, new_project) }
@@ -48,6 +45,63 @@ describe Issues::MoveService, services: true do
context 'issue movable' do
include_context 'user can move issue'
+ context 'move to new milestone' do
+ let(:new_issue) { move_service.execute(old_issue, new_project) }
+
+ context 'project milestone' do
+ let!(:milestone2) do
+ create(:milestone, project_id: new_project.id, title: 'v9.0')
+ end
+
+ it 'assigns milestone to new issue' do
+ expect(new_issue.reload.milestone.title).to eq 'v9.0'
+ expect(new_issue.reload.milestone).to eq(milestone2)
+ end
+ end
+
+ context 'group milestones' do
+ let!(:group) { create(:group, :private) }
+ let!(:group_milestone_1) do
+ create(:milestone, group_id: group.id, title: 'v9.0_group')
+ end
+
+ before do
+ old_issue.update(milestone: group_milestone_1)
+ old_project.update(namespace: group)
+ new_project.update(namespace: group)
+
+ group.add_users([user], GroupMember::DEVELOPER)
+ end
+
+ context 'when moving to a project of the same group' do
+ it 'keeps the same group milestone' do
+ expect(new_issue.reload.project).to eq(new_project)
+ expect(new_issue.reload.milestone).to eq(group_milestone_1)
+ end
+ end
+
+ context 'when moving to a project of a different group' do
+ let!(:group_2) { create(:group, :private) }
+
+ let!(:group_milestone_2) do
+ create(:milestone, group_id: group_2.id, title: 'v9.0_group')
+ end
+
+ before do
+ old_issue.update(milestone: group_milestone_1)
+ new_project.update(namespace: group_2)
+
+ group_2.add_users([user], GroupMember::DEVELOPER)
+ end
+
+ it 'assigns to new group milestone of same title' do
+ expect(new_issue.reload.project).to eq(new_project)
+ expect(new_issue.reload.milestone).to eq(group_milestone_2)
+ end
+ end
+ end
+ end
+
context 'generic issue' do
include_context 'issue move executed'
@@ -55,11 +109,6 @@ describe Issues::MoveService, services: true do
expect(new_issue.project).to eq new_project
end
- it 'assigns milestone to new issue' do
- expect(new_issue.reload.milestone.title).to eq 'v9.0'
- expect(new_issue.reload.milestone).to eq(milestone2)
- end
-
it 'assign labels to new issue' do
expected_label_titles = new_issue.reload.labels.map(&:title)
expect(expected_label_titles).to include 'label1'
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index c26642f5015..d0b991f19ab 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -253,13 +253,13 @@ describe Issues::UpdateService, services: true do
end
context 'when the milestone change' do
- before do
+ it 'marks todos as done' do
update_issue(milestone: create(:milestone))
- end
- it 'marks todos as done' do
expect(todo.reload.done?).to eq true
end
+
+ it_behaves_like 'system notes for milestones'
end
context 'when the labels change' do
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 711059208c1..19d9e4049fe 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe MergeRequests::MergeService, services: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
let(:project) { merge_request.project }
before do
@@ -133,18 +133,65 @@ describe MergeRequests::MergeService, services: true do
it { expect(todo).to be_done }
end
- context 'remove source branch by author' do
- let(:service) do
- merge_request.merge_params['force_remove_source_branch'] = '1'
- merge_request.save!
- MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+ context 'source branch removal' do
+ context 'when the source branch is protected' do
+ let(:service) do
+ MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
+ end
+
+ before do
+ create(:protected_branch, project: project, name: merge_request.source_branch)
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+ service.execute(merge_request)
+ end
end
- it 'removes the source branch' do
- expect(DeleteBranchService).to receive(:new)
- .with(merge_request.source_project, merge_request.author)
- .and_call_original
- service.execute(merge_request)
+ context 'when the source branch is the default branch' do
+ let(:service) do
+ MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
+ end
+
+ before do
+ allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+ service.execute(merge_request)
+ end
+ end
+
+ context 'when the source branch can be removed' do
+ context 'when MR author set the source branch to be removed' do
+ let(:service) do
+ merge_request.merge_params['force_remove_source_branch'] = '1'
+ merge_request.save!
+ MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+ end
+
+ it 'removes the source branch using the author user' do
+ expect(DeleteBranchService).to receive(:new)
+ .with(merge_request.source_project, merge_request.author)
+ .and_call_original
+ service.execute(merge_request)
+ end
+ end
+
+ context 'when MR merger set the source branch to be removed' do
+ let(:service) do
+ MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1')
+ end
+
+ it 'removes the source branch using the current user' do
+ expect(DeleteBranchService).to receive(:new)
+ .with(merge_request.source_project, user)
+ .and_call_original
+ service.execute(merge_request)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 671a932441e..74dcf152cb8 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -98,18 +98,52 @@ describe MergeRequests::RefreshService, services: true do
end
context 'push to origin repo target branch' do
- before do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
- reload_mrs
+ context 'when all MRs to the target branch had diffs' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it 'updates the merge state' do
+ expect(@merge_request.notes.last.note).to include('merged')
+ expect(@merge_request).to be_merged
+ expect(@fork_merge_request).to be_merged
+ expect(@fork_merge_request.notes.last.note).to include('merged')
+ expect(@build_failed_todo).to be_done
+ expect(@fork_build_failed_todo).to be_done
+ end
end
- it 'updates the merge state' do
- expect(@merge_request.notes.last.note).to include('merged')
- expect(@merge_request).to be_merged
- expect(@fork_merge_request).to be_merged
- expect(@fork_merge_request.notes.last.note).to include('merged')
- expect(@build_failed_todo).to be_done
- expect(@fork_build_failed_todo).to be_done
+ context 'when an MR to be closed was empty already' do
+ let!(:empty_fork_merge_request) do
+ create(:merge_request,
+ source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'master',
+ target_project: @project)
+ end
+
+ before do
+ # This spec already has a fake push, so pretend that we were targeting
+ # feature all along.
+ empty_fork_merge_request.update_columns(target_branch: 'feature')
+
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ empty_fork_merge_request.reload
+ end
+
+ it 'only updates the non-empty MRs' do
+ expect(@merge_request).to be_merged
+ expect(@merge_request.notes.last.note).to include('merged')
+
+ expect(@fork_merge_request).to be_merged
+ expect(@fork_merge_request.notes.last.note).to include('merged')
+
+ expect(empty_fork_merge_request).to be_open
+ expect(empty_fork_merge_request.merge_request_diff.state).to eq('empty')
+ expect(empty_fork_merge_request.notes).to be_empty
+ end
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index ec15b5cac14..be62584ec0e 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -296,13 +296,13 @@ describe MergeRequests::UpdateService, services: true do
end
context 'when the milestone change' do
- before do
+ it 'marks pending todos as done' do
update_merge_request({ milestone: create(:milestone) })
- end
- it 'marks pending todos as done' do
expect(pending_todo.reload).to be_done
end
+
+ it_behaves_like 'system notes for milestones'
end
context 'when the labels change' do
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
new file mode 100644
index 00000000000..8d1fe3ae2c1
--- /dev/null
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Milestones::DestroyService, services: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) }
+ let(:issue) { create(:issue, project: project, milestone: milestone) }
+ let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ def service
+ described_class.new(project, user, {})
+ end
+
+ describe '#execute' do
+ it 'deletes milestone' do
+ service.execute(milestone)
+
+ expect { milestone.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+
+ it 'deletes milestone id from issuables' do
+ service.execute(milestone)
+
+ expect(issue.reload.milestone).to be_nil
+ expect(merge_request.reload.milestone).to be_nil
+ end
+
+ context 'group milestones' do
+ let(:group) { create(:group) }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(namespace: group)
+ group.add_developer(user)
+ end
+
+ it { expect(service.execute(group_milestone)).to be_nil }
+
+ it 'does not update milestone issuables' do
+ expect(MergeRequests::UpdateService).not_to receive(:new)
+ expect(Issues::UpdateService).not_to receive(:new)
+
+ service.execute(group_milestone)
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
new file mode 100644
index 00000000000..dfe1ee7c41e
--- /dev/null
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe NotificationRecipientService, services: true do
+ set(:user) { create(:user) }
+ set(:project) { create(:empty_project, :public) }
+ set(:issue) { create(:issue, project: project) }
+
+ set(:watcher) do
+ watcher = create(:user)
+ setting = watcher.notification_settings_for(project)
+ setting.level = :watch
+ setting.save
+
+ watcher
+ end
+
+ subject { described_class.new(project) }
+
+ describe '#build_recipients' do
+ it 'does not modify the participants of the target' do
+ expect { subject.build_recipients(issue, user, action: :new_issue) }
+ .not_to change { issue.participants(user) }
+ end
+ end
+
+ describe '#build_new_note_recipients' do
+ set(:note) { create(:note_on_issue, noteable: issue, project: project) }
+
+ it 'does not modify the participants of the target' do
+ expect { subject.build_new_note_recipients(note) }
+ .not_to change { note.noteable.participants(note.author) }
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 76c52d55ae5..441a5276c56 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -30,6 +30,12 @@ describe Projects::TransferService, services: true do
transfer_project(project, user, group)
end
+ it 'expires full_path cache' do
+ expect(project).to receive(:expires_full_path_cache)
+
+ transfer_project(project, user, group)
+ end
+
it 'executes system hooks' do
expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks)
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index c9e63efbc14..a2db3f68ff7 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do
let(:content) { "/assign @#{developer.username}" }
context 'Issue' do
- it 'fetches assignee and populates assignee_id if content contains /assign' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
- expect(updates).to eq(assignee_ids: [developer.id])
+ expect(updates[:assignee_ids]).to match_array([developer.id])
end
end
context 'Merge Request' do
- it 'fetches assignee and populates assignee_id if content contains /assign' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
- expect(updates).to eq(assignee_id: developer.id)
+ expect(updates).to eq(assignee_ids: [developer.id])
end
end
end
@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do
end
context 'Issue' do
- it 'fetches assignee and populates assignee_id if content contains /assign' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
expect(updates[:assignee_ids]).to match_array([developer.id])
@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do
end
context 'Merge Request' do
- it 'fetches assignee and populates assignee_id if content contains /assign' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
- expect(updates).to eq(assignee_id: developer.id)
+ expect(updates).to eq(assignee_ids: [developer.id])
end
end
end
@@ -422,11 +422,11 @@ describe QuickActions::InterpretService, services: true do
end
context 'Merge Request' do
- it 'populates assignee_id: nil if content contains /unassign' do
- merge_request.update(assignee_id: developer.id)
+ it 'populates assignee_ids: [] if content contains /unassign' do
+ merge_request.update(assignee_ids: [developer.id])
_, updates = service.execute(content, merge_request)
- expect(updates).to eq(assignee_id: nil)
+ expect(updates).to eq(assignee_ids: [])
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 8d3dafafab2..60477b8e9ba 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe SystemNoteService, services: true do
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
let(:project) { create(:empty_project) }
let(:author) { create(:user) }
@@ -807,7 +807,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_commit_url(project.namespace, project, commit),
+ url: project_commit_url(project, commit),
title: "GitLab: Mentioned on commit - #{commit.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -833,7 +833,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_issue_url(project.namespace, project, issue),
+ url: project_issue_url(project, issue),
title: "GitLab: Mentioned on issue - #{issue.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -859,7 +859,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_snippet_url(project.namespace, project, snippet),
+ url: project_snippet_url(project, snippet),
title: "GitLab: Mentioned on snippet - #{snippet.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -1098,7 +1098,7 @@ describe SystemNoteService, services: true do
diff_id = merge_request.merge_request_diff.id
line_code = change_position.line_code(project.repository)
- expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code))
+ expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index fdef6fd5221..a497b8613bb 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -57,7 +57,7 @@ RSpec.configure do |config|
config.include StubGitlabCalls
config.include StubGitlabData
config.include ApiHelpers, :api
- config.include Rails.application.routes.url_helpers, type: :routing
+ config.include Gitlab::Routing, type: :routing
config.include MigrationsHelpers, :migration
config.infer_spec_type_from_file_location!
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
new file mode 100644
index 00000000000..3bd589d64b9
--- /dev/null
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -0,0 +1,79 @@
+shared_examples_for 'allows the "read_user" scope' do
+ context 'for personal access tokens' do
+ context 'when the requesting token has the "api" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['api'], user: user) }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token has the "read_user" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token does not have any required scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
+
+ it 'returns a "401" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'for doorkeeper (OAuth) tokens' do
+ let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
+
+ context 'when the requesting token has the "api" scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token has the "read_user" scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "read_user" }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token does not have any required scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "invalid" }
+
+ it 'returns a "403" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+end
+
+shared_examples_for 'does not allow the "read_user" scope' do
+ context 'when the requesting token has the "read_user" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "401" response' do
+ post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index 35d1e1cfc7d..ac0aaa524b7 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -17,14 +17,18 @@ module ApiHelpers
# => "/api/v2/issues?foo=bar&private_token=..."
#
# Returns the relative path to the requested API resource
- def api(path, user = nil, version: API::API.version)
+ def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
"/api/#{version}#{path}" +
# Normalize query string
(path.index('?') ? '' : '?') +
+ 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
- if user.respond_to?(:private_token)
+ elsif user.respond_to?(:private_token)
"&private_token=#{user.private_token}"
else
''
@@ -32,8 +36,14 @@ module ApiHelpers
end
# Temporary helper method for simplifying V3 exclusive API specs
- def v3_api(path, user = nil)
- api(path, user, version: 'v3')
+ def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil)
+ api(
+ path,
+ user,
+ version: 'v3',
+ personal_access_token: personal_access_token,
+ oauth_access_token: oauth_access_token
+ )
end
def ci_api(path, user = nil)
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
index b57a3493aff..3eb7bea3227 100644
--- a/spec/support/capybara_helpers.rb
+++ b/spec/support/capybara_helpers.rb
@@ -35,6 +35,11 @@ module CapybaraHelpers
visit 'about:blank'
visit url
end
+
+ # Simulate a browser restart by clearing the session cookie.
+ def clear_browser_session
+ page.driver.remove_cookie('_gitlab_session')
+ end
end
RSpec.configure do |config|
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 6e1eb5c678d..c0a5491a430 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -74,7 +74,9 @@ module CycleAnalyticsHelpers
def dummy_pipeline
@dummy_pipeline ||=
- Ci::Pipeline.new(sha: project.repository.commit('master').sha)
+ Ci::Pipeline.new(
+ sha: project.repository.commit('master').sha,
+ project: project)
end
def new_dummy_job(environment)
diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb
index 3de0460c3ca..b0fc8422857 100644
--- a/spec/support/fake_migration_classes.rb
+++ b/spec/support/fake_migration_classes.rb
@@ -1,3 +1,11 @@
class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration
include Gitlab::Database::RenameReservedPathsMigration::V1
+
+ def version
+ '20170316163845'
+ end
+
+ def name
+ "FakeRenameReservedPathMigrationV1"
+ end
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 50869099bb7..033e338fe61 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -17,7 +17,8 @@ shared_examples 'issuable record that supports quick actions in its description
project.team << [master, :master]
project.team << [assignee, :developer]
project.team << [guest, :guest]
- gitlab_sign_in(master)
+
+ sign_in(master)
end
after do
@@ -28,7 +29,12 @@ shared_examples 'issuable record that supports quick actions in its description
describe "new #{issuable_type}", js: true do
context 'with commands in the description' do
it "creates the #{issuable_type} and interpret commands accordingly" do
- visit public_send("new_namespace_project_#{issuable_type}_path", project.namespace, project, new_url_opts)
+ case issuable_type
+ when :merge_request
+ visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
+ when :issue
+ visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
+ end
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
click_button "Submit #{issuable_type}".humanize
@@ -105,8 +111,8 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot close #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ sign_out(:user)
+ sign_in(guest)
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -140,8 +146,8 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot reopen #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ sign_out(:user)
+ sign_in(guest)
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -170,8 +176,8 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot change title of #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ sign_out(:user)
+ sign_in(guest)
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 1cbb4134995..50fbbc7f55b 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,12 +1,12 @@
shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
it "has an RSS autodiscovery link tag with current_user's RSS token" do
- expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false)
+ expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false)
end
end
shared_examples "it has an RSS button with current_user's RSS token" do
it "shows the RSS button with current_user's RSS token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']")
+ expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']")
end
end
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
index 7335f74c0e9..c89389b90ca 100755
--- a/spec/support/generate-seed-repo-rb
+++ b/spec/support/generate-seed-repo-rb
@@ -15,7 +15,7 @@
require 'erb'
require 'tempfile'
-SOURCE = 'https://gitlab.com/gitlab-org/gitlab-git-test.git'.freeze
+SOURCE = File.expand_path('../gitlab-git-test.git', __FILE__).freeze
SCRIPT_NAME = 'generate-seed-repo-rb'.freeze
REPO_NAME = 'gitlab-git-test.git'.freeze
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index 2bf159002a0..89fb362cf14 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,8 +1,6 @@
-if Gitlab::GitalyClient.enabled?
- RSpec.configure do |config|
- config.before(:each) do |example|
- next if example.metadata[:skip_gitaly_mock]
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
- end
+RSpec.configure do |config|
+ config.before(:each) do |example|
+ next if example.metadata[:skip_gitaly_mock]
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
end
end
diff --git a/spec/support/gitlab-git-test.git/HEAD b/spec/support/gitlab-git-test.git/HEAD
new file mode 100644
index 00000000000..cb089cd89a7
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/spec/support/gitlab-git-test.git/README.md b/spec/support/gitlab-git-test.git/README.md
new file mode 100644
index 00000000000..f072cd421be
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/README.md
@@ -0,0 +1,16 @@
+# Gitlab::Git test repository
+
+This repository is used by (some of) the tests in spec/lib/gitlab/git.
+
+Do not add new large files to this repository. Otherwise we needlessly
+inflate the size of the gitlab-ce repository.
+
+## How to make changes to this repository
+
+- (if needed) clone `https://gitlab.com/gitlab-org/gitlab-ce.git` to your local machine
+- clone `gitlab-ce/spec/support/gitlab-git-test.git` locally (i.e. clone from your hard drive, not from the internet)
+- make changes in your local clone of gitlab-git-test
+- run `git push` which will push to your local source `gitlab-ce/spec/support/gitlab-git-test.git`
+- in gitlab-ce: run `spec/support/prepare-gitlab-git-test-for-commit`
+- in gitlab-ce: `git add spec/support/seed_repo.rb spec/support/gitlab-git-test.git`
+- commit your changes in gitlab-ce
diff --git a/spec/support/gitlab-git-test.git/config b/spec/support/gitlab-git-test.git/config
new file mode 100644
index 00000000000..03e2d1b1e0f
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ precomposeunicode = true
+[remote "origin"]
+ url = https://gitlab.com/gitlab-org/gitlab-git-test.git
diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx
new file mode 100644
index 00000000000..2253da798c4
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack
new file mode 100644
index 00000000000..3a61107c5b1
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs
new file mode 100644
index 00000000000..ce5ab1f705b
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/packed-refs
@@ -0,0 +1,18 @@
+# pack-refs with: peeled fully-peeled
+0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature
+12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix
+6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path
+58fa1a3af4de73ea83fe25a1ef1db8e0c56f67e5 refs/heads/fix-existing-submodule-dir
+40f4a7a617393735a95a0bb67b08385bc1e7c66d refs/heads/fix-mode
+9abd6a8c113a2dd76df3fdb3d58a8cec6db75f8d refs/heads/gitattributes
+46e1395e609395de004cacd4b142865ab0e52a29 refs/heads/gitattributes-updated
+4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master
+5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test
+f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0
+^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0
+^5937ac0a7beb003549fc5fd26fc247adbce4a52e
+10d64eed7760f2811ee2d64b44f1f7d3b364f17b refs/tags/v1.2.0
+^eb49186cfa5c4338011f5f590fac11bd66c5c631
+2ac1f24e253e08135507d0830508febaaccf02ee refs/tags/v1.2.1
+^fa1b1e6c004a68b7d8763b86455da9e6b23e36d6
diff --git a/spec/support/gitlab-git-test.git/refs/heads/.gitkeep b/spec/support/gitlab-git-test.git/refs/heads/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/refs/heads/.gitkeep
diff --git a/spec/support/gitlab-git-test.git/refs/tags/.gitkeep b/spec/support/gitlab-git-test.git/refs/tags/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/refs/tags/.gitkeep
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
index 03011535351..970fe10db2b 100644
--- a/spec/support/issuable_shared_examples.rb
+++ b/spec/support/issuable_shared_examples.rb
@@ -5,3 +5,34 @@ shared_examples 'cache counters invalidator' do
described_class.new(project, user, {}).execute(merge_request)
end
end
+
+shared_examples 'system notes for milestones' do
+ def update_issuable(opts)
+ issuable = try(:issue) || try(:merge_request)
+ described_class.new(project, user, opts).execute(issuable)
+ end
+
+ context 'group milestones' do
+ let(:group) { create(:group) }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(namespace: group)
+ create(:group_member, group: group, user: user)
+ end
+
+ it 'does not create system note' do
+ expect do
+ update_issuable(milestone: group_milestone)
+ end.not_to change { Note.system.count }
+ end
+ end
+
+ context 'project milestones' do
+ it 'creates system note' do
+ expect do
+ update_issuable(milestone: create(:milestone))
+ end.to change { Note.system.count }.by(1)
+ end
+ end
+end
diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb
index 85241793743..ffd72515f37 100644
--- a/spec/support/issue_helpers.rb
+++ b/spec/support/issue_helpers.rb
@@ -1,6 +1,6 @@
module IssueHelpers
def visit_issues(project, opts = {})
- visit namespace_project_issues_path project.namespace, project, opts
+ visit project_issues_path project, opts
end
def first_issue
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
index e70b3963d9d..a6ab03cb808 100644
--- a/spec/support/issue_tracker_service_shared_example.rb
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -8,15 +8,15 @@ end
RSpec.shared_examples 'allows project key on reference pattern' do |url_attr|
it 'allows underscores in the project name' do
- expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
end
it 'allows numbers in the project name' do
- expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+ expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
end
it 'requires the project name to begin with A-Z' do
- expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil
- expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+ expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
end
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 4c88958264b..b410a652126 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -15,14 +15,16 @@ module LoginHelpers
# user = create(:user)
# gitlab_sign_in(user)
def gitlab_sign_in(user_or_role, **kwargs)
- @user =
+ user =
if user_or_role.is_a?(User)
user_or_role
else
create(user_or_role)
end
- gitlab_sign_in_with(@user, **kwargs)
+ gitlab_sign_in_with(user, **kwargs)
+
+ user
end
def gitlab_sign_in_via(provider, user, uid)
@@ -35,13 +37,8 @@ module LoginHelpers
def gitlab_sign_out
find(".header-user-dropdown-toggle").click
click_link "Sign out"
- # check the sign_in button
- expect(page).to have_button('Sign in')
- end
- # Logout without JavaScript driver
- def gitlab_sign_out_direct
- page.driver.submit :delete, '/users/sign_out', {}
+ expect(page).to have_button('Sign in')
end
private
@@ -58,8 +55,16 @@ module LoginHelpers
check 'user_remember_me' if remember
click_button "Sign in"
+ end
+
+ def login_via(provider, user, uid, remember_me: false)
+ mock_auth_hash(provider, uid, user.email)
+ visit new_user_session_path
+ expect(page).to have_content('Sign in with')
+
+ check 'Remember Me' if remember_me
- Thread.current[:current_user] = user
+ click_link "oauth-login-#{provider}"
end
def mock_auth_hash(provider, uid, email)
@@ -108,6 +113,7 @@ module LoginHelpers
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages)
- expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
+ allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
+ allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
end
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
new file mode 100644
index 00000000000..ff60bd0c0ae
--- /dev/null
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -0,0 +1,108 @@
+# AccessMatchersForController
+#
+# For testing authorize_xxx in controller.
+module AccessMatchersForController
+ extend RSpec::Matchers::DSL
+ include Warden::Test::Helpers
+
+ EXPECTED_STATUS_CODE_ALLOWED = [200, 201, 302].freeze
+ EXPECTED_STATUS_CODE_DENIED = [401, 404].freeze
+
+ def emulate_user(role, membership = nil)
+ case role
+ when :admin
+ user = create(:admin)
+ sign_in(user)
+ when :user
+ user = create(:user)
+ sign_in(user)
+ when :external
+ user = create(:user, external: true)
+ sign_in(user)
+ when :visitor
+ user = nil
+ when User
+ user = role
+ sign_in(user)
+ when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest
+ raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership
+
+ user = create_user_by_membership(role, membership)
+ sign_in(user)
+ else
+ raise ArgumentError, "cannot emulate user #{role}"
+ end
+
+ user
+ end
+
+ def create_user_by_membership(role, membership)
+ if role == :owner && membership.owner
+ user = membership.owner
+ else
+ user = create(:user)
+ membership.public_send(:"add_#{role}", user)
+ end
+ user
+ end
+
+ def description_for(role, type, expected, result)
+ "be #{type} for #{role}. Expected: #{expected.join(',')} Got: #{result}"
+ end
+
+ def update_owner(objects, user)
+ return unless objects
+
+ objects.each do |object|
+ if object.respond_to?(:owner)
+ object.update_attribute(:owner, user)
+ elsif object.respond_to?(:user)
+ object.update_attribute(:user, user)
+ else
+ raise ArgumentError, "cannot own this object #{object}"
+ end
+ end
+ end
+
+ matcher :be_allowed_for do |role|
+ match do |action|
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
+ action.call
+
+ EXPECTED_STATUS_CODE_ALLOWED.include?(response.status)
+ end
+
+ chain :of do |membership|
+ @membership = membership
+ end
+
+ chain :own do |*objects|
+ @objects = objects
+ end
+
+ description { description_for(role, 'allowed', EXPECTED_STATUS_CODE_ALLOWED, response.status) }
+ supports_block_expectations
+ end
+
+ matcher :be_denied_for do |role|
+ match do |action|
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
+ action.call
+
+ EXPECTED_STATUS_CODE_DENIED.include?(response.status)
+ end
+
+ chain :of do |membership|
+ @membership = membership
+ end
+
+ chain :own do |*objects|
+ @objects = objects
+ end
+
+ description { description_for(role, 'denied', EXPECTED_STATUS_CODE_DENIED, response.status) }
+ supports_block_expectations
+ end
+end
diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb
new file mode 100644
index 00000000000..ea806352422
--- /dev/null
+++ b/spec/support/matchers/be_utf8.rb
@@ -0,0 +1,9 @@
+RSpec::Matchers.define :be_utf8 do |_|
+ match do |actual|
+ actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
+ end
+
+ description do
+ "be a String with encoding UTF-8"
+ end
+end
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
index 326b85eabd0..772adff4626 100644
--- a/spec/support/merge_request_helpers.rb
+++ b/spec/support/merge_request_helpers.rb
@@ -1,6 +1,6 @@
module MergeRequestHelpers
def visit_merge_requests(project, opts = {})
- visit namespace_project_merge_requests_path project.namespace, project, opts
+ visit project_merge_requests_path project, opts
end
def first_merge_request
diff --git a/spec/support/prepare-gitlab-git-test-for-commit b/spec/support/prepare-gitlab-git-test-for-commit
new file mode 100755
index 00000000000..3047786a599
--- /dev/null
+++ b/spec/support/prepare-gitlab-git-test-for-commit
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+abort unless [
+ system('spec/support/generate-seed-repo-rb', out: 'spec/support/seed_repo.rb'),
+ system('spec/support/unpack-gitlab-git-test')
+].all?
+
+exit if ARGV.first != '--check-for-changes'
+
+git_status = IO.popen(%w[git status --porcelain], &:read)
+abort unless $?.success?
+
+puts git_status
+
+if git_status.lines.grep(%r{^.. spec/support/gitlab-git-test.git}).any?
+ abort "error: detected changes in gitlab-git-test.git"
+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 1d11512ef82..421a51fc336 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,7 +1,7 @@
RSpec.shared_examples "protected tags > access control > CE" do
ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected tags that #{access_type_name} can create" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('master')
@@ -22,7 +22,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
end
it "allows updating protected tags so that #{access_type_name} can create them" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('master')
diff --git a/spec/support/routing_helpers.rb b/spec/support/routing_helpers.rb
new file mode 100644
index 00000000000..af1f4760804
--- /dev/null
+++ b/spec/support/routing_helpers.rb
@@ -0,0 +1,3 @@
+RSpec.configure do |config|
+ config.include GitlabRoutingHelper
+end
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 47b5f556e66..8731847592b 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -9,7 +9,7 @@ TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze
TEST_BROKEN_REPO_PATH = 'broken-repo.git'.freeze
module SeedHelper
- GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze
+ GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __FILE__).freeze
def ensure_seeds
if File.exist?(SEED_STORAGE_PATH)
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
new file mode 100644
index 00000000000..96c821b26f7
--- /dev/null
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -0,0 +1,9 @@
+shared_examples 'issue sidebar stays collapsed on mobile' do
+ before do
+ resize_screen_xs
+ end
+
+ it 'keeps the sidebar collapsed' do
+ expect(page).not_to have_css('.right-sidebar.right-sidebar-collapsed')
+ end
+end
diff --git a/spec/support/protected_branches/access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index 287d6bb13c3..66e598e2691 100644
--- a/spec/support/protected_branches/access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,7 +1,7 @@
-RSpec.shared_examples "protected branches > access control > CE" do
+shared_examples "protected branches > access control > CE" do
ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
@@ -21,7 +21,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
it "allows updating protected branches so that #{access_type_name} can push to them" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
@@ -46,7 +46,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can merge to" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
@@ -66,7 +66,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
it "allows updating protected branches so that #{access_type_name} can merge to them" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index 575d3451150..5478fea4e64 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -3,3 +3,9 @@ require 'sidekiq/testing/inline'
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ServerMiddleware
end
+
+RSpec.configure do |config|
+ config.after(:each, :sidekiq) do
+ Sidekiq::Worker.clear_all
+ end
+end
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
index 2999bcd9fb1..b8928867174 100644
--- a/spec/support/stub_env.rb
+++ b/spec/support/stub_env.rb
@@ -1,15 +1,33 @@
+# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV
- def stub_env(key, value)
- allow(ENV).to receive(:[]).and_call_original unless @env_already_stubbed
- @env_already_stubbed ||= true
+ def stub_env(key_or_hash, value = nil)
+ init_stub unless env_stubbed?
+ if key_or_hash.is_a? Hash
+ key_or_hash.each { |k, v| add_stubbed_value(k, v) }
+ else
+ add_stubbed_value key_or_hash, value
+ end
+ end
+
+ private
+
+ STUBBED_KEY = '__STUBBED__'.freeze
+
+ def add_stubbed_value(key, value)
allow(ENV).to receive(:[]).with(key).and_return(value)
+ allow(ENV).to receive(:fetch).with(key).and_return(value)
+ allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val|
+ value || default_val
+ end
+ end
+
+ def env_stubbed?
+ ENV[STUBBED_KEY]
end
-end
-# It's possible that the state of the class variables are not reset across
-# test runs.
-RSpec.configure do |config|
- config.after(:each) do
- @env_already_stubbed = nil
+ def init_stub
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:fetch).and_call_original
+ add_stubbed_value(STUBBED_KEY, true)
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 1c5267c290b..0cae5620920 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -69,7 +69,7 @@ module TestEnv
# Setup GitLab shell for test instance
setup_gitlab_shell
- setup_gitaly if Gitlab::GitalyClient.enabled?
+ setup_gitaly
# Create repository for FactoryGirl.create(:project)
setup_factory_repo
@@ -120,18 +120,21 @@ module TestEnv
end
def setup_gitlab_shell
- unless File.directory?(Gitlab.config.gitlab_shell.path)
- unless system('rake', 'gitlab:shell:install')
- raise 'Can`t clone gitlab-shell'
- end
+ shell_needs_update = component_needs_update?(Gitlab.config.gitlab_shell.path,
+ Gitlab::Shell.version_required)
+
+ unless !shell_needs_update || system('rake', 'gitlab:shell:install')
+ raise 'Can`t clone gitlab-shell'
end
end
def setup_gitaly
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
+ gitaly_needs_update = component_needs_update?(gitaly_dir,
+ Gitlab::GitalyClient.expected_server_version)
- unless !gitaly_needs_update?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
+ unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
raise "Can't clone gitaly"
end
@@ -261,13 +264,13 @@ module TestEnv
end
end
- def gitaly_needs_update?(gitaly_dir)
- gitaly_version = File.read(File.join(gitaly_dir, 'VERSION')).strip
+ def component_needs_update?(component_folder, expected_version)
+ version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions
# (`=branch_name`), but that actually makes sure the server is always based
# on the latest branch revision.
- gitaly_version != Gitlab::GitalyClient.expected_server_version
+ version != expected_version
rescue Errno::ENOENT
true
end
diff --git a/spec/support/unpack-gitlab-git-test b/spec/support/unpack-gitlab-git-test
new file mode 100755
index 00000000000..d5b4912457d
--- /dev/null
+++ b/spec/support/unpack-gitlab-git-test
@@ -0,0 +1,38 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+
+REPO = 'spec/support/gitlab-git-test.git'.freeze
+PACK_DIR = REPO + '/objects/pack'
+GIT = %W[git --git-dir=#{REPO}].freeze
+BASE_PACK = 'pack-691247af2a6acb0b63b73ac0cb90540e93614043'.freeze
+
+def main
+ unpack
+ # We want to store the refs in a packed-refs file because if we don't
+ # they can get mangled by filesystems.
+ abort unless system(*GIT, *%w[pack-refs --all])
+ abort unless system(*GIT, 'fsck')
+end
+
+# We don't want contributors to commit new pack files because those
+# create unnecessary churn.
+def unpack
+ pack_files = Dir[File.join(PACK_DIR, '*')].reject do |pack|
+ pack.start_with?(File.join(PACK_DIR, BASE_PACK))
+ end
+ return if pack_files.empty?
+
+ pack_files.each do |pack|
+ unless pack.end_with?('.pack')
+ FileUtils.rm(pack)
+ next
+ end
+
+ File.open(pack, 'rb') do |open_pack|
+ File.unlink(pack)
+ abort unless system(*GIT, 'unpack-objects', in: open_pack)
+ end
+ end
+end
+
+main
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index 72323da2838..6a4738ba443 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -16,8 +16,7 @@ describe 'ci/status/_badge', :view do
end
it 'has link to build details page' do
- details_path = namespace_project_job_path(
- project.namespace, project, build)
+ details_path = project_job_path(project, build)
render_status(build)
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 4052dbf8df3..98c7de9b709 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/merge_requests/show/_commits.html.haml' do
+describe 'projects/merge_requests/_commits.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
@@ -25,10 +25,7 @@ describe 'projects/merge_requests/show/_commits.html.haml' do
render
commit = source_project.commit(merge_request.source_branch)
- href = namespace_project_commit_path(
- source_project.namespace,
- source_project,
- commit)
+ href = project_commit_path(source_project, commit)
expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
end
diff --git a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 4f698a34ab5..1e9bdf9108f 100644
--- a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/merge_requests/_new_submit.html.haml', :view do
+describe 'projects/merge_requests/creations/_new_submit.html.haml', :view do
let(:merge_request) { create(:merge_request) }
let!(:pipeline) { create(:ci_empty_pipeline) }
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 85939429feb..4f6e3474634 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BackgroundMigrationWorker do
+describe BackgroundMigrationWorker, :sidekiq do
describe '.perform' do
it 'performs a background migration' do
expect(Gitlab::BackgroundMigration)
@@ -10,4 +10,35 @@ describe BackgroundMigrationWorker do
described_class.new.perform('Foo', [10, 20])
end
end
+
+ describe '.perform_bulk' do
+ it 'enqueues background migrations in bulk' do
+ Sidekiq::Testing.fake! do
+ described_class.perform_bulk([['Foo', [1]], ['Foo', [2]]])
+
+ expect(described_class.jobs.count).to eq 2
+ expect(described_class.jobs).to all(include('enqueued_at'))
+ end
+ end
+ end
+
+ describe '.perform_bulk_in' do
+ context 'when delay is valid' do
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ described_class.perform_bulk_in(1.minute, [['Foo', [1]], ['Foo', [2]]])
+
+ expect(described_class.jobs.count).to eq 2
+ expect(described_class.jobs).to all(include('at'))
+ end
+ end
+ end
+
+ context 'when delay is invalid' do
+ it 'raises an ArgumentError exception' do
+ expect { described_class.perform_bulk_in(-60, [['Foo']]) }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
end
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index 1d8da68883b..bed5c5e2ecb 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -30,20 +30,6 @@ describe ExpireBuildInstanceArtifactsWorker do
expect(build.reload.artifacts_file_identifier).to be_nil
end
end
-
- context 'when associated project was removed' do
- let(:build) do
- create(:ci_build, :artifacts, artifacts_expiry) do |build|
- build.project.pending_delete = true
- end
- end
-
- it 'does not remove artifacts' do
- expect do
- build.reload.artifacts_file
- end.not_to raise_error
- end
- end
end
context 'with not yet expired artifacts' do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index cc9bc29c6cc..a8f4bb72acf 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,7 +4,7 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
- let(:project_identifier) { "project-#{project.id}" }
+ let(:gl_repository) { "project-#{project.id}" }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
@@ -19,22 +19,14 @@ describe PostReceive do
end
context 'with a non-existing project' do
- let(:project_identifier) { "project-123456789" }
+ let(:gl_repository) { "project-123456789" }
let(:error_message) do
- "Triggered hook for non-existing project with identifier \"#{project_identifier}\""
+ "Triggered hook for non-existing project with gl_repository \"#{gl_repository}\""
end
it "returns false and logs an error" do
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}")
- expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be(false)
- end
- end
-
- context "with an absolute path as the project identifier" do
- it "searches the project by full path" do
- expect(Project).to receive(:find_by_full_path).with(project.full_path, follow_redirects: true).and_call_original
-
- described_class.new.perform(pwd(project), key_id, base64_changes)
+ expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be(false)
end
end
@@ -49,7 +41,7 @@ describe PostReceive do
it "calls GitTagPushService" do
expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
expect_any_instance_of(GitTagPushService).not_to receive(:execute)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -59,7 +51,7 @@ describe PostReceive do
it "calls GitTagPushService" do
expect_any_instance_of(GitPushService).not_to receive(:execute)
expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -69,12 +61,12 @@ describe PostReceive do
it "does not call any of the services" do
expect_any_instance_of(GitPushService).not_to receive(:execute)
expect_any_instance_of(GitTagPushService).not_to receive(:execute)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
context "gitlab-ci.yml" do
- subject { described_class.new.perform(project_identifier, key_id, base64_changes) }
+ subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
context "creates a Ci::Pipeline for every change" do
before do
@@ -111,7 +103,7 @@ describe PostReceive do
it 'calls SystemHooksService' do
expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
end
@@ -119,7 +111,7 @@ describe PostReceive do
context "webhook" do
it "fetches the correct project" do
expect(Project).to receive(:find_by).with(id: project.id.to_s)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
it "does not run if the author is not in the project" do
@@ -129,7 +121,7 @@ describe PostReceive do
expect(project).not_to receive(:execute_hooks)
- expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be_falsey
+ expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be_falsey
end
it "asks the project to trigger all hooks" do
@@ -137,18 +129,14 @@ describe PostReceive do
expect(project).to receive(:execute_hooks).twice
expect(project).to receive(:execute_services).twice
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
it "enqueues a UpdateMergeRequestsWorker job" do
allow(Project).to receive(:find_by).and_return(project)
expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
- described_class.new.perform(project_identifier, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
-
- def pwd(project)
- File.join(Gitlab.config.repositories.storages.default['path'], project.path_with_namespace)
- end
end
diff --git a/yarn.lock b/yarn.lock
index b902d5235d0..b04eebe60af 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -265,6 +265,15 @@ babel-core@^6.22.1, babel-core@^6.23.0:
slash "^1.0.0"
source-map "^0.5.0"
+babel-eslint@^7.2.1:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f"
+ dependencies:
+ babel-code-frame "^6.22.0"
+ babel-traverse "^6.23.1"
+ babel-types "^6.23.0"
+ babylon "^6.16.1"
+
babel-generator@^6.18.0, babel-generator@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
@@ -816,10 +825,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
+babylon@^6.11.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
+babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
+ version "6.16.1"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
+
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"