summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CHANGELOG.md2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--VERSION2
-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.js7
-rw-r--r--app/assets/javascripts/dispatcher.js34
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js1
-rw-r--r--app/assets/javascripts/helpers/issuables_helper.js27
-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/main.js13
-rw-r--r--app/assets/javascripts/merge_request.js14
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue30
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_flag.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_legends.vue10
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js11
-rw-r--r--app/assets/javascripts/notes.js4
-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/shortcuts.js2
-rw-r--r--app/assets/javascripts/signin_tabs_memoizer.js4
-rw-r--r--app/assets/javascripts/single_file_diff.js37
-rw-r--r--app/assets/javascripts/snippets_list.js10
-rw-r--r--app/assets/javascripts/star.js8
-rw-r--r--app/assets/javascripts/subscription_select.js8
-rw-r--r--app/assets/javascripts/task_list.js5
-rw-r--r--app/assets/javascripts/todos.js5
-rw-r--r--app/assets/javascripts/tree.js14
-rw-r--r--app/assets/javascripts/usage_ping.js5
-rw-r--r--app/assets/javascripts/user.js3
-rw-r--r--app/assets/javascripts/user_tabs.js5
-rw-r--r--app/assets/javascripts/username_validator.js4
-rw-r--r--app/assets/javascripts/version_check_image.js3
-rw-r--r--app/assets/javascripts/visibility_select.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue18
-rw-r--r--app/assets/javascripts/wikis.js6
-rw-r--r--app/assets/javascripts/zen_mode.js22
-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/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/variables.scss38
-rw-r--r--app/assets/stylesheets/new_nav.scss77
-rw-r--r--app/assets/stylesheets/new_sidebar.scss118
-rw-r--r--app/assets/stylesheets/pages/environments.scss20
-rw-r--r--app/assets/stylesheets/pages/issuable.scss25
-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/pipeline_schedules.scss81
-rw-r--r--app/assets/stylesheets/pages/profile.scss3
-rw-r--r--app/assets/stylesheets/performance_bar.scss (renamed from vendor/assets/stylesheets/peek.scss)45
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/application_controller.rb17
-rw-r--r--app/controllers/concerns/issuable_collections.rb2
-rw-r--r--app/controllers/concerns/membership_actions.rb2
-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.rb77
-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/omniauth_callbacks_controller.rb8
-rw-r--r--app/controllers/projects/environments_controller.rb2
-rw-r--r--app/controllers/projects/group_links_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/controllers/projects/milestones_controller.rb29
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb19
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb2
-rw-r--r--app/controllers/projects/project_members_controller.rb23
-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/variables_controller.rb48
-rw-r--r--app/finders/concerns/created_at_filter.rb8
-rw-r--r--app/finders/issuable_finder.rb33
-rw-r--r--app/finders/milestones_finder.rb60
-rw-r--r--app/finders/projects_finder.rb9
-rw-r--r--app/finders/users_finder.rb3
-rw-r--r--app/helpers/gitlab_routing_helper.rb6
-rw-r--r--app/helpers/issuables_helper.rb57
-rw-r--r--app/helpers/milestones_helper.rb14
-rw-r--r--app/helpers/nav_helper.rb1
-rw-r--r--app/helpers/performance_bar_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb18
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/models/appearance.rb2
-rw-r--r--app/models/application_setting.rb57
-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.rb29
-rw-r--r--app/models/ci/group_variable.rb13
-rw-r--r--app/models/ci/pipeline.rb21
-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.rb2
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb1
-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/internal_id.rb3
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/milestoneish.rb16
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/routable.rb4
-rw-r--r--app/models/concerns/sha_attribute.rb2
-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.rb2
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/global_label.rb2
-rw-r--r--app/models/global_milestone.rb47
-rw-r--r--app/models/group.rb20
-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.rb12
-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.rb26
-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.rb4
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/project.rb180
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-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.rb4
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/service.rb10
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/user.rb63
-rw-r--r--app/policies/ci/pipeline_schedule_policy.rb10
-rw-r--r--app/policies/group_policy.rb2
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/presenters/ci/group_variable_presenter.rb25
-rw-r--r--app/presenters/ci/variable_presenter.rb25
-rw-r--r--app/serializers/label_entity.rb3
-rw-r--r--app/services/boards/create_service.rb19
-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/issuable_base_service.rb15
-rw-r--r--app/services/issues/move_service.rb14
-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/reopen_service.rb2
-rw-r--r--app/services/milestones/update_service.rb4
-rw-r--r--app/services/quick_actions/interpret_service.rb26
-rw-r--r--app/validators/variable_duplicates_validator.rb13
-rw-r--r--app/views/admin/application_settings/_form.html.haml16
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/projects/show.html.haml6
-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/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/devise/shared/_omniauth_box.html.haml5
-rw-r--r--app/views/groups/_settings_head.html.haml5
-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/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/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml6
-rw-r--r--app/views/layouts/application.html.haml3
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml4
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml2
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml11
-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/projects/blob/viewers/_readme.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/empty.html.haml3
-rw-r--r--app/views/projects/issues/show.html.haml23
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml33
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml8
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_variable_row.html.haml17
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml8
-rw-r--r--app/views/projects/project_members/_group_members.html.haml18
-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.haml2
-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/services/_form.html.haml21
-rw-r--r--app/views/projects/settings/_head.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/projects/tree/show.html.haml5
-rw-r--r--app/views/projects/variables/show.html.haml10
-rw-r--r--app/views/shared/_service_settings.html.haml9
-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/_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/_search_bar.html.haml2
-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.haml35
-rw-r--r--app/views/shared/milestones/_tabs.html.haml9
-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/workers/background_migration_worker.rb20
-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/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/29893-change-menu-locations.yml3
-rw-r--r--changelogs/unreleased/31982-liberation-mono-linux.yml4
-rw-r--r--changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml4
-rw-r--r--changelogs/unreleased/33360-generate-kubeconfig.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/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.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/34727-simplified-member-settings.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/dm-readme-auxiliary-blob-viewer-without-wiki.yml4
-rw-r--r--changelogs/unreleased/enable-polling-env.yml4
-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-user-agent-details-api.yml4
-rw-r--r--changelogs/unreleased/feature-user-datetime-search-api-mysql.yml4
-rw-r--r--changelogs/unreleased/fix-mrs-merged-immediately.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/issue-description-gfm.yml4
-rw-r--r--changelogs/unreleased/issue_30126_be.yml4
-rw-r--r--changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml4
-rw-r--r--changelogs/unreleased/tc-follow-up-mia.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--config/application.rb13
-rw-r--r--config/gitlab.yml.example50
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/8_gitaly.rb8
-rw-r--r--config/initializers/doorkeeper.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.rb5
-rw-r--r--config/webpack.config.js2
-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/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/20170628080858_migrate_stage_id_reference_in_background.rb33
-rw-r--r--db/schema.rb93
-rw-r--r--doc/README.md2
-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.md5
-rw-r--r--doc/api/issues.md27
-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/snippets.md32
-rw-r--r--doc/api/users.md6
-rw-r--r--doc/ci/variables/README.md41
-rw-r--r--doc/ci/yaml/README.md47
-rw-r--r--doc/development/README.md1
-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/downgrade_ee_to_ce/README.md13
-rw-r--r--doc/install/database_mysql.md13
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md53
-rw-r--r--doc/update/9.2-to-9.3.md13
-rw-r--r--doc/update/9.3-to-9.4.md2
-rw-r--r--doc/user/project/integrations/kubernetes.md1
-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--features/group/milestones.feature4
-rw-r--r--features/project/active_tab.feature11
-rw-r--r--features/steps/group/milestones.rb9
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--lib/api/entities.rb30
-rw-r--r--lib/api/features.rb29
-rw-r--r--lib/api/helpers.rb3
-rw-r--r--lib/api/helpers/internal_helpers.rb35
-rw-r--r--lib/api/internal.rb3
-rw-r--r--lib/api/issues.rb16
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/pipeline_schedules.rb9
-rw-r--r--lib/api/project_snippets.rb16
-rw-r--r--lib/api/projects.rb120
-rw-r--r--lib/api/snippets.rb16
-rw-r--r--lib/api/users.rb6
-rw-r--r--lib/api/variables.rb8
-rw-r--r--lib/banzai/filter/reference_filter.rb2
-rw-r--r--lib/gitlab/background_migration/migrate_build_stage_id_reference.rb19
-rw-r--r--lib/gitlab/badge/metadata.rb2
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb14
-rw-r--r--lib/gitlab/conflict/file.rb2
-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/ee_compat_check.rb2
-rw-r--r--lib/gitlab/email/message/repository_push.rb2
-rw-r--r--lib/gitlab/git/blob.rb45
-rw-r--r--lib/gitlab/git/repository.rb16
-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/import_export/import_export.yml1
-rw-r--r--lib/gitlab/kubernetes.rb39
-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.rb6
-rw-r--r--lib/gitlab/routes/legacy_builds.rb36
-rw-r--r--lib/gitlab/routing.rb25
-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.rb2
-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/en/gitlab.po18
-rw-r--r--locale/gitlab.pot22
-rw-r--r--locale/zh_TW/gitlab.po38
-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/rubocop.rb4
-rw-r--r--spec/controllers/dashboard/labels_controller_spec.rb25
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb118
-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/environments_controller_spec.rb6
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb6
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb34
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb330
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb15
-rw-r--r--spec/controllers/projects/settings/members_controller_spec.rb14
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb5
-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/features/abuse_report_spec.rb2
-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.rb2
-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.rb6
-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.rb2
-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.rb2
-rw-r--r--spec/features/atom/issues_spec.rb2
-rw-r--r--spec/features/auto_deploy_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb8
-rw-r--r--spec/features/boards/issue_ordering_spec.rb2
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/boards/new_issue_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/boards/sub_group_project_spec.rb2
-rw-r--r--spec/features/calendar_spec.rb2
-rw-r--r--spec/features/ci_lint_spec.rb2
-rw-r--r--spec/features/container_registry_spec.rb2
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb8
-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/projects_spec.rb10
-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/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.rb2
-rw-r--r--spec/features/discussion_comments/issue_spec.rb2
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb2
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb2
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb2
-rw-r--r--spec/features/explore/groups_list_spec.rb2
-rw-r--r--spec/features/explore/new_menu_spec.rb4
-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.rb10
-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.rb2
-rw-r--r--spec/features/issues/award_emoji_spec.rb2
-rw-r--r--spec/features/issues/award_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb4
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb4
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb9
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb4
-rw-r--r--spec/features/issues/form_spec.rb6
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb14
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues/note_polling_spec.rb6
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb2
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/issues/todo_spec.rb2
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb32
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/merge_requests/assign_issues_spec.rb2
-rw-r--r--spec/features/merge_requests/award_spec.rb2
-rw-r--r--spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb2
-rw-r--r--spec/features/merge_requests/cherry_pick_spec.rb2
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb2
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb4
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb2
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb2
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb6
-rw-r--r--spec/features/merge_requests/diffs_spec.rb4
-rw-r--r--spec/features/merge_requests/discussion_spec.rb2
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/form_spec.rb4
-rw-r--r--spec/features/merge_requests/merge_commit_message_toggle_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb4
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb2
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb2
-rw-r--r--spec/features/merge_requests/target_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/toggle_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb2
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_requests/user_sees_system_notes_spec.rb2
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb18
-rw-r--r--spec/features/merge_requests/versions_spec.rb2
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb2
-rw-r--r--spec/features/merge_requests/widget_spec.rb9
-rw-r--r--spec/features/merge_requests/wip_message_spec.rb8
-rw-r--r--spec/features/milestone_spec.rb25
-rw-r--r--spec/features/milestones/show_spec.rb2
-rw-r--r--spec/features/oauth_login_spec.rb112
-rw-r--r--spec/features/participants_autocomplete_spec.rb2
-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.rb4
-rw-r--r--spec/features/projects/badges/coverage_spec.rb4
-rw-r--r--spec/features/projects/badges/list_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb6
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/branches/new_branch_ref_dropdown_spec.rb2
-rw-r--r--spec/features/projects/commit/builds_spec.rb2
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commit/rss_spec.rb4
-rw-r--r--spec/features/projects/compare_spec.rb2
-rw-r--r--spec/features/projects/deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb2
-rw-r--r--spec/features/projects/edit_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb2
-rw-r--r--spec/features/projects/environments/environments_spec.rb2
-rw-r--r--spec/features/projects/features_visibility_spec.rb10
-rw-r--r--spec/features/projects/files/browse_files_spec.rb2
-rw-r--r--spec/features/projects/files/creating_a_file_spec.rb2
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb2
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb2
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb2
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb2
-rw-r--r--spec/features/projects/files/find_files_spec.rb2
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb2
-rw-r--r--spec/features/projects/group_links_spec.rb2
-rw-r--r--spec/features/projects/guest_navigation_menu_spec.rb2
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb2
-rw-r--r--spec/features/projects/issuable_templates_spec.rb9
-rw-r--r--spec/features/projects/issues/list_spec.rb2
-rw-r--r--spec/features/projects/issues/rss_spec.rb5
-rw-r--r--spec/features/projects/jobs_spec.rb6
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/labels/subscription_spec.rb2
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb4
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/main/rss_spec.rb4
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb4
-rw-r--r--spec/features/projects/members/group_links_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb2
-rw-r--r--spec/features/projects/members/list_spec.rb2
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb2
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/sorting_spec.rb2
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb9
-rw-r--r--spec/features/projects/merge_request_button_spec.rb4
-rw-r--r--spec/features/projects/merge_requests/list_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestones_sorting_spec.rb2
-rw-r--r--spec/features/projects/pages_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb97
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/project_settings_spec.rb2
-rw-r--r--spec/features/projects/ref_switcher_spec.rb2
-rw-r--r--spec/features/projects/services/jira_service_spec.rb2
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/slack_service_spec.rb2
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb2
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb4
-rw-r--r--spec/features/projects/shortcuts_spec.rb2
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/show_spec.rb2
-rw-r--r--spec/features/projects/snippets_spec.rb2
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb2
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/tree/rss_spec.rb4
-rw-r--r--spec/features/projects/user_create_dir_spec.rb2
-rw-r--r--spec/features/projects/view_on_env_spec.rb12
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb2
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb25
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_project_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb2
-rw-r--r--spec/features/projects_spec.rb12
-rw-r--r--spec/features/protected_branches_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb2
-rw-r--r--spec/features/reportable_note/commit_spec.rb2
-rw-r--r--spec/features/reportable_note/issue_spec.rb2
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb2
-rw-r--r--spec/features/reportable_note/snippets_spec.rb2
-rw-r--r--spec/features/runners_spec.rb2
-rw-r--r--spec/features/search_spec.rb2
-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_snippets_spec.rb2
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb2
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb2
-rw-r--r--spec/features/tags/master_updates_tag_spec.rb2
-rw-r--r--spec/features/tags/master_views_tags_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb2
-rw-r--r--spec/features/user_callout_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb23
-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.rb18
-rw-r--r--spec/finders/issues_finder_spec.rb17
-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/helpers/gitlab_routing_helper_spec.rb6
-rw-r--r--spec/javascripts/close_reopen_report_toggle_spec.js270
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml5
-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/merge_request_spec.js33
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js1
-rw-r--r--spec/javascripts/monitoring/mock_data.js1
-rw-r--r--spec/javascripts/monitoring/monitoring_column_spec.js3
-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/ci/gitlab_ci_yaml_processor_spec.rb9
-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/job_spec.rb4
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb79
-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/safe_model_attributes.yml13
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb24
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb92
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb53
-rw-r--r--spec/migrations/migrate_stage_id_reference_in_background_spec.rb68
-rw-r--r--spec/models/application_setting_spec.rb154
-rw-r--r--spec/models/blob_viewer/readme_spec.rb49
-rw-r--r--spec/models/ci/build_spec.rb90
-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.rb33
-rw-r--r--spec/models/ci/variable_spec.rb7
-rw-r--r--spec/models/concerns/each_batch_spec.rb53
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb31
-rw-r--r--spec/models/forked_project_link_spec.rb6
-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.rb20
-rw-r--r--spec/models/milestone_spec.rb44
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb48
-rw-r--r--spec/models/project_spec.rb159
-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/features_spec.rb14
-rw-r--r--spec/requests/api/internal_spec.rb74
-rw-r--r--spec/requests/api/issues_spec.rb23
-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.rb65
-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.rb29
-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/services/boards/create_service_spec.rb2
-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/refresh_service_spec.rb54
-rw-r--r--spec/services/merge_requests/update_service_spec.rb6
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb16
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/capybara_helpers.rb5
-rw-r--r--spec/support/cycle_analytics_helpers.rb4
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb15
-rw-r--r--spec/support/features/rss_shared_examples.rb4
-rw-r--r--spec/support/gitaly.rb10
-rw-r--r--spec/support/issuable_shared_examples.rb31
-rw-r--r--spec/support/login_helpers.rb26
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb28
-rw-r--r--spec/support/sidekiq.rb6
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb33
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb14
787 files changed, 9206 insertions, 2688 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a3ce1de50c2..f7ab0259448 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -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
@@ -556,3 +557,4 @@ gitlab_git_test:
SETUP_DB: "false"
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
+ <<: *except-docs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d2adb47a80..c15a59d25d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ entry.
## 9.3.4 (2017-07-03)
-- No changes.
+- Update gitlab-shell to 5.1.1 !12615
## 9.3.3 (2017-06-30)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index a803cc227fe..04a373efe6b 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.14.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_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 7814de99fb2..2c248a45175 100644
--- a/Gemfile
+++ b/Gemfile
@@ -386,7 +386,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 70abc0669df..f356024506c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -278,7 +278,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)
@@ -980,7 +980,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)
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/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 1be9df19c81..6a008112203 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -2,6 +2,7 @@
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
+import SingleFileDiff from './single_file_diff';
const UNFOLD_COUNT = 20;
let isBound = false;
@@ -10,7 +11,11 @@ class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
- $diffFile.singleFileDiff();
+ $diffFile.each((index, file) => {
+ if (!$.data(file, 'singleFileDiff')) {
+ $.data(file, 'singleFileDiff', new SingleFileDiff(file));
+ }
+ });
FilesCommentButton.init($diffFile);
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 4247540de22..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':
@@ -315,7 +322,7 @@ import initExperimentalFlags from './experimental_flags';
new gl.Members();
new UsersSelect();
break;
- case 'projects:settings:members:show':
+ case 'projects:project_members:index':
new gl.MemberExpirationDate('.js-access-expiration-date-groups');
new GroupsSelect();
new gl.MemberExpirationDate();
@@ -377,7 +384,7 @@ 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();
@@ -393,6 +400,7 @@ import initExperimentalFlags from './experimental_flags';
initSettingsPanels();
break;
case 'projects:settings:ci_cd:show':
+ case 'groups:settings:ci_cd:show':
new gl.ProjectVariables();
break;
case 'ci:lints:create':
@@ -429,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();
@@ -481,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);
@@ -513,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/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 2c56b718212..6cb9cfe1382 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -30,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'));
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_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/main.js b/app/assets/javascripts/main.js
index fe752d95b90..892b3fab1c6 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -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/');
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/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
index 0f33581ec52..c376baea79c 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_column.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -105,9 +105,9 @@
this.measurements = measurements.small;
}
this.data = query.result[0].values;
- this.unitOfDisplay = query.unit || 'N/A';
+ this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.columnData.y_label || 'Values';
- this.legendTitle = query.legend || 'Average';
+ 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;
@@ -159,12 +159,12 @@
const xAxis = d3.svg.axis()
.scale(axisXScale)
- .ticks(measurements.ticks)
+ .ticks(measurements.xTicks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
- .ticks(measurements.ticks)
+ .ticks(measurements.yTicks)
.orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
@@ -172,8 +172,12 @@
const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
.selectAll('.tick')
- .each(function createTickLines() {
- d3.select(this).select('line').attr('x2', width);
+ .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()
@@ -215,16 +219,16 @@
};
</script>
<template>
- <div
+ <div
:class="classType">
- <h5
+ <h5
class="text-center graph-title">
{{columnData.title}}
</h5>
<div
class="prometheus-svg-container"
:style="paddingBottomRootSvg">
- <svg
+ <svg
:viewBox="outterViewBox"
ref="baseSvg">
<g
@@ -235,7 +239,7 @@
class="y-axis"
transform="translate(70, 20)">
</g>
- <monitoring-legends
+ <monitoring-legends
:graph-width="graphWidth"
:graph-height="graphHeight"
:margin="margin"
@@ -245,7 +249,7 @@
:y-axis-label="yAxisLabel"
:metric-usage="metricUsage"
/>
- <svg
+ <svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
@@ -263,7 +267,7 @@
stroke-width="2"
transform="translate(-5, 20)">
</path>
- <rect
+ <rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
@@ -277,7 +281,7 @@
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
- <monitoring-flag
+ <monitoring-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
:current-y-coordinate="currentYCoordinate"
diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
index 180a771415b..5a0e50fcab3 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_flag.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
@@ -87,14 +87,14 @@
</rect>
<text
class="text-metric text-metric-bold"
- x="8"
+ x="16"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
- class="text-metric-date"
- x="8"
+ class="text-metric"
+ x="16"
y="15"
transform="translate(-5, 20)">
{{formatDate}}
diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
index b30ed3cc889..922a5e1bf0e 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_legends.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
@@ -109,13 +109,13 @@
</text>
<rect
class="rect-axis-text"
- :x="xPosition + 50"
+ :x="xPosition + 60"
:y="graphHeight - 80"
- width="50"
+ width="35"
height="50">
</rect>
<text
- class="label-axis-text"
+ class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
@@ -131,13 +131,13 @@
<text
class="text-metric-title"
x="50"
- :y="graphHeight - 40">
+ :y="graphHeight - 25">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
- :y="graphHeight - 25">
+ :y="graphHeight - 10">
{{metricUsage}}
</text>
</g>
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
index a60d2522f49..62cd19c86e1 100644
--- a/app/assets/javascripts/monitoring/utils/measurements.js
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -8,14 +8,14 @@ export default {
},
legends: {
width: 15,
- height: 30,
+ height: 25,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
- legendOffset: 52,
+ legendOffset: 35,
},
large: { // This covers both md and lg screen sizes
margin: {
@@ -26,14 +26,15 @@ export default {
},
legends: {
width: 20,
- height: 35,
+ height: 30,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
- legendOffset: 55,
+ legendOffset: 38,
},
- ticks: 3,
+ xTicks: 8,
+ yTicks: 3,
};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 555b8c8a65c..1a68c5bca00 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -21,7 +21,7 @@ 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;
@@ -71,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'
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/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/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js
index 3997a695d15..20255398047 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/signin_tabs_memoizer.js
@@ -6,7 +6,7 @@ import AccessorUtilities from './lib/utils/accessor';
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
-class ActiveTabMemoizer {
+export default class SigninTabsMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
@@ -51,5 +51,3 @@ class ActiveTabMemoizer {
return window.localStorage.getItem(this.currentTabKey);
}
}
-
-window.ActiveTabMemoizer = ActiveTabMemoizer;
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 00d04ce0c33..4505a79a2df 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -2,18 +2,13 @@
import FilesCommentButton from './files_comment_button';
-window.SingleFileDiff = (function() {
- var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+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>';
- WRAPPER = '<div class="diff-content"></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>';
-
- COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
-
- function SingleFileDiff(file) {
+export default class SingleFileDiff {
+ constructor(file) {
this.file = file;
this.toggleDiff = this.toggleDiff.bind(this);
this.content = $('.diff-content', this.file);
@@ -37,7 +32,7 @@ window.SingleFileDiff = (function() {
}).bind(this));
}
- SingleFileDiff.prototype.toggleDiff = function($target, cb) {
+ 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) {
@@ -58,9 +53,9 @@ window.SingleFileDiff = (function() {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(cb);
}
- };
+ }
- SingleFileDiff.prototype.getContentHTML = function(cb) {
+ getContentHTML(cb) {
this.collapsedContent.hide();
this.loadingContent.show();
$.get(this.diffForPath, (function(_this) {
@@ -84,15 +79,5 @@ window.SingleFileDiff = (function() {
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));
- }
- });
-};
+ }
+}
diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js
index da7b9e08447..3b6d999b1c3 100644
--- a/app/assets/javascripts/snippets_list.js
+++ b/app/assets/javascripts/snippets_list.js
@@ -1,9 +1,9 @@
-/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
-
-window.gl.SnippetsList = function() {
- var $holder = $('.snippets-list-holder');
+function SnippetsList() {
+ const $holder = $('.snippets-list-holder');
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
-};
+}
+
+window.gl.SnippetsList = SnippetsList;
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 840ae1edd9d..6d38124f1c1 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,8 +1,8 @@
/* 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 */
-window.Star = (function() {
- function Star() {
+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);
@@ -23,6 +23,4 @@ window.Star = (function() {
new Flash('Star toggle failed. Try again later.', 'alert');
});
}
-
- return Star;
-})();
+}
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index a48434181b6..37e39ce5477 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,7 +1,7 @@
/* 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 */
-window.SubscriptionSelect = (function() {
- function SubscriptionSelect() {
+class SubscriptionSelect {
+ constructor() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
fieldName = $(el).data("field-name");
@@ -28,6 +28,6 @@ window.SubscriptionSelect = (function() {
});
});
}
+}
- return SubscriptionSelect;
-})();
+window.SubscriptionSelect = SubscriptionSelect;
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 77ae6109bc6..7777ed1c3dc 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,7 +1,7 @@
-/* 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 */
-window.TreeView = (function() {
- function TreeView() {
+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)
@@ -22,7 +22,7 @@ window.TreeView = (function() {
$('span.log_loading:first').removeClass('hide');
}
- TreeView.prototype.initKeyNav = function() {
+ initKeyNav() {
var li, liSelected;
li = $("tr.tree-item");
liSelected = null;
@@ -60,7 +60,5 @@ window.TreeView = (function() {
}
}
});
- };
-
- return TreeView;
-})();
+ }
+}
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 3ab9ef5408e..9ef94ac7616 100644
--- a/app/assets/javascripts/user.js
+++ b/app/assets/javascripts/user.js
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
import Cookies from 'js-cookie';
+import UserTabs from './user_tabs';
class User {
constructor({ action }) {
@@ -17,7 +18,7 @@ class User {
}
initTabs() {
- return new window.gl.UserTabs({
+ return new UserTabs({
parentEl: '.user-profile',
action: this.action
});
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
index be70f4cb4e2..f8e23c8624d 100644
--- a/app/assets/javascripts/user_tabs.js
+++ b/app/assets/javascripts/user_tabs.js
@@ -60,7 +60,7 @@ content on the Users#show page.
</div>
*/
-class UserTabs {
+export default class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
@@ -171,6 +171,3 @@ class UserTabs {
return this.$parentEl.find('.nav-links .active a').data('action');
}
}
-
-window.gl = window.gl || {};
-window.gl.UserTabs = UserTabs;
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js
index abe6c30f4f3..a348d69153c 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/username_validator.js
@@ -8,7 +8,7 @@ const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
-class UsernameValidator {
+export default class UsernameValidator {
constructor() {
this.inputElement = $('#new_user_username');
this.inputDomElement = this.inputElement.get(0);
@@ -129,5 +129,3 @@ class UsernameValidator {
$inputErrorMessage.show();
}
}
-
-window.UsernameValidator = UsernameValidator;
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 b6bbbaa0936..0c928d0d5f6 100644
--- a/app/assets/javascripts/visibility_select.js
+++ b/app/assets/javascripts/visibility_select.js
@@ -1,4 +1,4 @@
-class VisibilitySelect {
+export default class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
@@ -19,6 +19,3 @@ class VisibilitySelect {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
}
-
-window.gl = window.gl || {};
-window.gl.VisibilitySelect = VisibilitySelect;
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/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/wikis.js b/app/assets/javascripts/wikis.js
index 03d183ebd84..00676bcb0b3 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -1,10 +1,9 @@
-/* eslint-disable no-param-reassign */
/* global Breakpoints */
import 'vendor/jquery.nicescroll';
import './breakpoints';
-class Wikis {
+export default class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
@@ -63,6 +62,3 @@ class Wikis {
}
}
}
-
-window.gl = window.gl || {};
-window.gl.Wikis = Wikis;
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 08f80735e93..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
@@ -35,8 +35,8 @@ window.Dropzone = Dropzone;
// **Target** a.js-zen-leave
//
-window.ZenMode = (function() {
- function ZenMode() {
+export default class ZenMode {
+ constructor() {
this.active_backdrop = null;
this.active_textarea = null;
$(document).on('click', '.js-zen-enter', function(e) {
@@ -66,7 +66,7 @@ window.ZenMode = (function() {
});
}
- ZenMode.prototype.enter = function(backdrop) {
+ enter(backdrop) {
Mousetrap.pause();
this.active_backdrop = $(backdrop);
this.active_backdrop.addClass('fullscreen');
@@ -74,9 +74,9 @@ window.ZenMode = (function() {
// Prevent a user-resized textarea from persisting to fullscreen
this.active_textarea.removeAttr('style');
return this.active_textarea.focus();
- };
+ }
- ZenMode.prototype.exit = function() {
+ exit() {
if (this.active_textarea) {
Mousetrap.unpause();
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
@@ -85,13 +85,11 @@ window.ZenMode = (function() {
this.active_backdrop = null;
return Dropzone.forElement('.div-dropzone').enable();
}
- };
+ }
- ZenMode.prototype.scrollTo = function(zen_area) {
+ scrollTo(zen_area) {
return $.scrollTo(zen_area, 0, {
offset: -150
});
- };
-
- return ZenMode;
-})();
+ }
+}
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/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/variables.scss b/app/assets/stylesheets/framework/variables.scss
index da4d91511e0..3f032776d82 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -74,11 +74,17 @@ $red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
-$purple-600: #6e49cb;
-$purple-650: #5c35ae;
-$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);
@@ -153,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;
@@ -264,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";
/*
@@ -443,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;
@@ -575,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;
@@ -594,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/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index bfb7a0c7e25..73cb3a7cf4c 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -4,7 +4,7 @@
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 {
@@ -24,11 +24,9 @@ header.navbar-gitlab-new {
> a {
display: flex;
align-items: center;
- 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;
@@ -45,9 +43,8 @@ header.navbar-gitlab-new {
&:hover,
&:focus {
- color: currentColor;
+ color: $tanuki-yellow;
text-decoration: none;
- border-bottom-color: $white-light;
}
}
}
@@ -71,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) {
@@ -101,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,
@@ -120,6 +117,7 @@ header.navbar-gitlab-new {
li {
.badge {
box-shadow: none;
+ font-weight: 600;
}
}
}
@@ -133,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;
}
}
@@ -165,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;
}
}
@@ -207,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 {
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index 17f23f7fce3..96459fe31cc 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -2,6 +2,15 @@
@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 {
@@ -17,24 +26,45 @@ $new-sidebar-width: 220px;
}
.context-header {
- background-color: $gray-normal;
border-bottom: 1px solid $border-color;
font-weight: 600;
display: flex;
align-items: center;
- padding: 10px 14px;
+ padding: 10px 16px 10px 10px;
+ color: $gl-text-color;
.avatar-container {
flex: 0 0 40px;
}
&:hover {
- background-color: $border-color;
+ 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;
@@ -42,6 +72,7 @@ $new-sidebar-width: 220px;
color: $gl-text-color-secondary;
text-align: center;
align-self: center;
+ transition: color 100ms linear;
}
}
@@ -54,11 +85,15 @@ $new-sidebar-width: 220px;
bottom: 0;
left: 0;
overflow: auto;
- background-color: $gray-light;
- border-right: 1px solid $border-color;
+ background-color: $gray-normal;
+ box-shadow: inset -2px 0 0 $border-color;
+
+ a {
+ text-decoration: none;
+ }
ul {
- padding: 0;
+ padding-left: 0;
list-style: none;
}
@@ -67,13 +102,18 @@ $new-sidebar-width: 220px;
a {
display: block;
- padding: 12px 14px;
+ padding: 12px 16px;
+ color: $inactive-color;
}
}
- a {
- color: $gl-text-color;
- text-decoration: none;
+ li.active {
+ box-shadow: inset 4px 0 0 $active-border;
+
+ > a {
+ color: $active-color;
+ font-weight: 700;
+ }
}
@media (max-width: $screen-xs-max) {
@@ -83,22 +123,28 @@ $new-sidebar-width: 220px;
.sidebar-sub-level-items {
display: none;
+ padding-bottom: 8px;
> li {
a {
- padding: 12px 24px;
- color: $gl-text-color-light;
+ font-size: 12px;
+ padding: 8px 16px 8px 24px;
- &:hover {
- color: $gl-text-color;
- background-color: $border-color;
+ &:hover,
+ &:focus {
+ background: $active-hover-background;
+ color: $active-hover-color;
}
}
&.active {
- > a {
- color: $purple-650;
- font-weight: 600;
+ a {
+ &,
+ &:hover,
+ &:focus {
+ background: $active-background;
+ color: $active-color;
+ }
}
}
}
@@ -108,35 +154,31 @@ $new-sidebar-width: 220px;
> li {
.badge {
float: right;
- background-color: $border-color;
- color: $gl-text-color;
+ background-color: $inactive-badge-background;
+ color: $inactive-color;
}
&.active {
- > a {
- background-color: $purple-600;
- color: $white-light;
- font-weight: 600;
- }
+ background: $active-background;
.badge {
- background-color: $purple-700;
- color: $white-light;
+ color: $active-color;
+ font-weight: 600;
}
.sidebar-sub-level-items {
- background-color: $gray-normal;
- border-left: 6px solid $purple-600;
display: block;
}
}
- &:not(.active) > a:hover {
- background-color: $border-color;
+ > a:hover {
+ background-color: $hover-background;
+ color: $hover-color;
.badge {
- transition: background-color 100ms linear;
- background-color: $gray-normal;
+ transition: background-color 100ms linear, color 100ms linear;
+ background-color: $indigo-500;
+ color: $hover-color;
}
}
}
@@ -155,3 +197,13 @@ $new-sidebar-width: 220px;
// 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/environments.scss b/app/assets/stylesheets/pages/environments.scss
index e9a679b20c2..00ebf4e26ac 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -187,8 +187,7 @@
}
.text-metric {
- font-weight: 600;
- font-size: 14px;
+ font-size: 12px;
}
.selected-metric-line {
@@ -232,10 +231,6 @@
width: 100%;
padding: 0;
padding-bottom: 100%;
-
- .text-metric-bold {
- font-weight: 600;
- }
}
.prometheus-svg-container > svg {
@@ -250,6 +245,10 @@
stroke-width: 0;
}
+ .text-metric-bold {
+ font-weight: 600;
+ }
+
.label-axis-text,
.text-metric-usage {
fill: $black;
@@ -269,6 +268,15 @@
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,
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 47f50083726..56a4b53ed61 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -799,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/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/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/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/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index f978ce478c7..1cc060e4de8 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -126,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/application_controller.rb b/app/controllers/application_controller.rb
index b4c0cd0487f..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
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 650ec1e326a..693e2f6365c 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -47,7 +47,7 @@ module IssuableCollections
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/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 6b1d418fc9a..5c10d7bc261 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -2,13 +2,13 @@ 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
@@ -22,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)
@@ -74,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/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/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 919d021b59c..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)
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 8fc614b414d..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 project_settings_members_path(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 project_settings_members_path(project), status: 302
+ redirect_to project_project_members_path(project), status: 302
end
format.js { head :ok }
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 5de0f828010..6602b204fcb 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -17,8 +17,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
end
def merge_request_params
- params.require(:merge_request)
- .permit(merge_request_params_attributes)
+ params.require(:merge_request).permit(merge_request_params_attributes)
end
def merge_request_params_attributes
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index a80562e77ce..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,12 +41,13 @@ 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
+ if @milestone.valid?
redirect_to project_milestone_path(@project, @milestone)
else
render "new"
@@ -85,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/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index 0d967a7e691..ec7c645df5a 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -1,11 +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]
- before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update]
+ 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
@@ -53,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
@@ -66,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_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index f13884307b6..9d24ebe2138 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -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 57a6686f66c..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 project_settings_members_path(@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 = project_settings_members_path(@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(project_settings_members_path(project),
+ redirect_to(project_project_members_path(project),
notice: notice)
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/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 573d1c05622..6a825137564 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,4 +1,5 @@
class Projects::VariablesController < Projects::ApplicationController
+ before_action :variable, only: [:show, :update, :destroy]
before_action :authorize_admin_build!
layout 'project_settings'
@@ -8,43 +9,52 @@ class Projects::VariablesController < Projects::ApplicationController
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 project_variables_path(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 project_settings_ci_cd_path(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 project_settings_ci_cd_path(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/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 7bc2117f61e..2e5a6493134 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,6 +19,8 @@
# iids: integer[]
#
class IssuableFinder
+ include CreatedAtFilter
+
NONE = '0'.freeze
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
@@ -32,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)
@@ -42,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)
@@ -147,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
@@ -331,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
@@ -408,18 +413,6 @@ 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
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/users_finder.rb b/app/finders/users_finder.rb
index 07deceb827b..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
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index b5f4bbe97dc..0517a699ae0 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -2,6 +2,10 @@
module GitlabRoutingHelper
extend ActiveSupport::Concern
+ included do
+ Gitlab::Routing.includes_helpers(self)
+ end
+
# 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
@@ -97,7 +101,7 @@ module GitlabRoutingHelper
## Members
def project_members_url(project, *args)
- project_project_members_url(project)
+ project_project_members_url(project, *args)
end
def project_member_path(project_member, *args)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b5366519ed9..d0c518f81f7 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -245,6 +245,53 @@ module IssuablesHelper
@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?
@@ -270,8 +317,6 @@ module IssuablesHelper
issue_template_names
when MergeRequest
merge_request_template_names
- else
- raise 'Unknown issuable type!'
end
end
@@ -301,4 +346,12 @@ module IssuablesHelper
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/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 8c7851dcfc2..f8860bfee99 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -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
@@ -147,4 +149,14 @@ module MilestonesHelper
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 e589ed4e56d..b769462abc2 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -23,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/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 5022b291f7f..25969adb649 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -267,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
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8c44f4b0934..fd7ab59ce64 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -75,7 +75,7 @@ module SearchHelper
{ 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_settings_members_path(@project) },
+ { category: "Current Project", label: "Members", url: project_project_members_path(@project) },
{ category: "Current Project", label: "Wiki", url: project_wikis_path(@project) }
]
else
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..14516091495 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
@@ -234,6 +234,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 +337,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 2e7a80d308b..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
@@ -186,6 +186,12 @@ module Ci
# 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
@@ -194,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
@@ -216,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
@@ -370,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 364858964b0..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.
@@ -326,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?
@@ -377,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 c806a0585dc..fb0fbb43fb1 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -8,7 +8,7 @@ module Ci
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'
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 0b8d0ff881a..cf0fe04ddaf 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -2,6 +2,7 @@ module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
include HasVariable
+ include Presentable
belongs_to :project
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/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 41c8b525273..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
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 ee108f010a6..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
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index c28974a3cdf..67ecf470f7e 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -3,6 +3,8 @@ module ShaAttribute
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,
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 eb24ff00ce3..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
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/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 a6fdb30f84c..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
@@ -248,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 a97e88f76f6..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
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 808212c780c..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
@@ -516,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" +
@@ -839,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 672eab94c07..15713fc5f6d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -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,
diff --git a/app/models/note.rb b/app/models/note.rb
index dfd435bcdf6..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
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 315e1a6cb17..d0e02e4e585 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,7 +237,7 @@ 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
scope :pending_delete, -> { where(pending_delete: true) }
@@ -518,9 +536,19 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id)
end
+ remove_import_data
+ 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)
@@ -1015,7 +1043,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
@@ -1229,7 +1258,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
@@ -1315,7 +1350,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
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 5e31f393bbe..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?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 8af642b44aa..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
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 beaadbbd1ab..dfca0031af8 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8c24e722a8b..10b429c707e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -931,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)
@@ -1078,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 b3aa7bb986e..09d5ff46618 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -30,7 +30,7 @@ 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
diff --git a/app/models/user.rb b/app/models/user.rb
index 0febae84873..4b01c2f19f0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -12,6 +12,7 @@ class User < ActiveRecord::Base
include TokenAuthenticatable
include IgnorableColumn
include FeatureGate
+ include CreatedAtFilterable
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -41,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
@@ -67,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
@@ -92,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
@@ -211,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) }
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/group_policy.rb b/app/policies/group_policy.rb
index dcb37416ca3..6defab75fce 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -31,6 +31,8 @@ class GroupPolicy < BasePolicy
rule { master }.policy do
enable :create_projects
enable :admin_milestones
+ enable :admin_pipeline
+ enable :admin_build
end
rule { owner }.policy do
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 7cbca63fab4..323131c0f7e 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -162,7 +162,6 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
- enable :update_pipeline_schedule
enable :create_merge_request
enable :create_wiki
enable :push_code
@@ -188,7 +187,6 @@ class ProjectPolicy < BasePolicy
enable :admin_build
enable :admin_container_image
enable :admin_pipeline
- enable :admin_pipeline_schedule
enable :admin_environment
enable :admin_deployment
enable :admin_pages
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/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/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/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/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/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/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/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index e4dfe87e614..6f82159e6c7 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -146,32 +146,6 @@ module QuickActions
end
end
- desc do
- "Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
- end
- explanation do |users|
- users = issuable.allows_multiple_assignees? ? users : users.take(1)
- "Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
- end
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
- end
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |assignee_param|
- extract_users(assignee_param)
- end
- command :reassign do |users|
- @updates[:assignee_ids] =
- if issuable.allows_multiple_assignees?
- users.map(&:id)
- else
- [users.last.id]
- end
- end
-
desc 'Set milestone'
explanation do |milestone|
"Sets the milestone to #{milestone.to_reference}." if milestone
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 5f5eeb8b9a9..7f1e13c7989 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -333,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/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/show.html.haml b/app/views/admin/projects/show.html.haml
index fb9057b2db5..7b1b15cfeb8 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -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/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 4ce6a828812..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 project_variable_path(@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 project_variable_path(@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/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/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/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/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/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/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index abb6dc2e9f3..6ad22958df3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,7 +30,7 @@
= 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"
@@ -44,7 +44,7 @@
= 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/_page.html.haml b/app/views/layouts/_page.html.haml
index 1a9f5401a78..cc9219cb6fe 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -12,10 +12,12 @@
.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
- - if show_new_nav?
- = render "layouts/nav/breadcrumbs"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
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/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index 40c1ca7b53e..d7a9e530983 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -4,7 +4,7 @@
= icon('wrench')
.project-title Admin Area
%ul.sidebar-top-level-items
- = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do
+ = 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
@@ -26,7 +26,7 @@
= link_to admin_groups_path, title: 'Groups' do
%span
Groups
- = nav_link path: 'builds#index' do
+ = nav_link path: 'jobs#index' do
= link_to admin_jobs_path, title: 'Jobs' do
%span
Jobs
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index b7ac04cc3e5..7b68d11041e 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,5 +1,5 @@
.nav-sidebar
- = link_to group_path(@group), title: 'Group', class: 'context-header' do
+ = 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
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index 6e483353a2d..cc731db3cc1 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,6 +1,6 @@
.nav-sidebar
- can_edit = can?(current_user, :admin_project, @project)
- = link_to project_path(@project), title: 'Project', class: 'context-header' do
+ = 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
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 14deb46eee3..fb90bb4b472 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -57,16 +57,17 @@
%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 project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do
- %span
- Settings
-# Shortcut to Project > Activity
%li.hidden
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/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
index 507f44d4745..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", project_wikis_path(viewer.project)
+ = link_to "the wiki", get_project_wiki_path(viewer.project)
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index cf8dffc8957..c764e35dd2a 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -12,4 +12,4 @@
- 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/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/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index cf8493faba8..a57844f974e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -30,24 +30,23 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_issue
- %li
- = link_to 'Edit', edit_project_issue_path(@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_project_issue_path(@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_project_issue_path(@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_project_issue_path(@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'
+
+ = 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_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
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index 3182aecd0a8..a2e819fb3a7 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -1,3 +1,5 @@
+- 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.
@@ -15,21 +17,24 @@
.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
+ .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'
- %li
- = link_to 'Edit', edit_project_merge_request_path(@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_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
- Edit
+
+ - 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/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 08ccd57c81a..97c0407a01d 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -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/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 151d794fa92..255d7ef38e0 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -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/_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 7ed467c8841..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 project_settings_members_path(@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 03b33eb2da7..f6ca8d5a921 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -12,4 +12,4 @@
.form-actions
= button_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", project_settings_members_path(@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/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 7eab428bb2e..b842fd57cf3 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -11,16 +11,17 @@
.col-lg-9
= 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', project_settings_integrations_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index b5773acb5a4..15ba09b10ba 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -9,10 +9,6 @@
= 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
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/show.html.haml b/app/views/projects/show.html.haml
index ea780b1cb83..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, 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'
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 3fb247c5ceb..130980556c1 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -5,7 +5,6 @@
= 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/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/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/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/_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/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index ae890567225..bdb573cb8fd 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -19,7 +19,7 @@
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
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 ecc8b42979c..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,26 +18,32 @@
&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_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index e2d1695b7c3..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)
+ - 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: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ = 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 20a12613cfc..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, 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
+- 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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-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-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/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/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_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/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/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/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/config/application.rb b/config/application.rb
index a9a961d7520..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,7 +106,7 @@ 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"
@@ -167,6 +168,8 @@ module Gitlab
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')
@@ -176,11 +179,7 @@ module Gitlab
end
end
- Gitlab::Routing.url_helpers.include project_url_helpers
- Gitlab::Routing.url_helpers.extend project_url_helpers
-
- GitlabRoutingHelper.include project_url_helpers
- GitlabRoutingHelper.extend project_url_helpers
+ Gitlab::Routing.add_helpers(project_url_helpers)
end
end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 4b81fd90f59..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.
@@ -619,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 cb11d2c34f4..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
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/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/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 0d0a8dff25e..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]
@@ -253,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
@@ -387,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 cbb0a899638..c3fdca59a86 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -70,7 +70,7 @@ 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',
},
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/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/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 d52dab5417e..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: 20170703102400) 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: 20170703102400) 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: 20170703102400) 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"
@@ -693,6 +721,21 @@ ActiveRecord::Schema.define(version: 20170703102400) 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
@@ -800,7 +843,7 @@ ActiveRecord::Schema.define(version: 20170703102400) 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"
@@ -811,10 +854,12 @@ ActiveRecord::Schema.define(version: 20170703102400) 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"}
@@ -971,6 +1016,7 @@ ActiveRecord::Schema.define(version: 20170703102400) 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
@@ -1020,6 +1066,7 @@ ActiveRecord::Schema.define(version: 20170703102400) 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"
@@ -1086,6 +1133,7 @@ ActiveRecord::Schema.define(version: 20170703102400) 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
@@ -1527,48 +1575,79 @@ ActiveRecord::Schema.define(version: 20170703102400) 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 fa755852304..9b81c409570 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -167,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
@@ -179,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 558869255cc..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.
@@ -61,7 +61,8 @@ POST /features/:name
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
-Note that `feature_group` and `user` are mutually exclusive.
+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/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/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/variables/README.md b/doc/ci/variables/README.md
index eef96f3194f..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)
@@ -40,6 +41,7 @@ future GitLab releases.**
| **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 a2a07c37ced..58993c52dcd 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -55,6 +55,7 @@
- [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/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 37e9b3101ca..bc75dc1447e 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -39,6 +39,9 @@ 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`;
@@ -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/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/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 097b996ec31..910539acc70 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -164,6 +164,19 @@ 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
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/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/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/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/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 7288dc87005..915d766dd60 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -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/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/lib/api/entities.rb b/lib/api/entities.rb
index 9cd078a2356..f4796f311a5 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
@@ -502,12 +508,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
@@ -831,7 +845,7 @@ module API
end
class Cache < Grape::Entity
- expose :key, :untracked, :paths
+ expose :key, :untracked, :paths, :policy
end
class Credentials < Grape::Entity
@@ -874,5 +888,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 21745916463..9385c6ca174 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -14,14 +14,12 @@ module API
end
end
- def gate_target(params)
- if params[:feature_group]
- Feature.group(params[:feature_group])
- elsif params[:user]
- User.find_by_username(params[:user])
- else
- gate_value(params)
- 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
@@ -42,18 +40,25 @@ module API
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'
- mutually_exclusive :feature_group, :user
end
post ':name' do
feature = Feature.get(params[:name])
- target = gate_target(params)
+ targets = gate_targets(params)
value = gate_value(params)
case value
when true
- feature.enable(target)
+ if targets.present?
+ targets.each { |target| feature.enable(target) }
+ else
+ feature.enable
+ end
when false
- feature.disable(target)
+ if targets.present?
+ targets.each { |target| feature.disable(target) }
+ else
+ feature.disable
+ end
else
feature.enable_percentage_of_time(value)
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a2a661b205c..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
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/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..d419d345ec5 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -41,7 +41,9 @@ 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
+ .inc_notes_with_associations
+ .preload(:target_project, :author, :assignee, :milestone, :merge_request_diff)
merge_requests.reorder(args[:order_by] => args[:sort])
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 d0bd64b2972..c459257158d 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -10,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'
@@ -35,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]
-
- 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
+ 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]
+
+ 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
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 88bca235692..c469751c31c 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -48,6 +48,8 @@ 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
@@ -55,6 +57,10 @@ module API
get do
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
authorized = can?(current_user, :read_users_list)
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/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/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/metadata.rb b/lib/gitlab/badge/metadata.rb
index 86c193650fb..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::Routing.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/conflict/file.rb b/lib/gitlab/conflict/file.rb
index d2b4e6e209e..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)
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/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 42fc2a4ea19..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
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index ffe4f3ca95f..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
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index dd5a4d5ad55..e51966313d4 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -568,11 +568,17 @@ module Gitlab
# 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
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/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/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/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 057f32eaef7..c1ee20b6977 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -30,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/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 073af685a09..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
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/en/gitlab.po b/locale/en/gitlab.po
index bda3fc09e85..7065b7a635f 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -619,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 ""
@@ -628,12 +634,18 @@ 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 ""
@@ -1057,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/gitlab.pot b/locale/gitlab.pot
index 9f1caeddaa7..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-28 13:32+0200\n"
-"PO-Revision-Date: 2017-06-28 13:32+0200\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"
@@ -620,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 ""
@@ -629,12 +635,18 @@ 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 ""
@@ -1058,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/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 91cac543a25..fa0b3b339fa 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -7,7 +7,7 @@ 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"
@@ -18,6 +18,15 @@ msgstr ""
"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} 送交"
@@ -66,9 +75,24 @@ 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 "瀏覽檔案"
@@ -175,6 +199,9 @@ msgstr "建立 %{file_name}"
msgid "Commits"
msgstr "更動記錄 (commit) "
+msgid "Commits feed"
+msgstr "更動摘要 (commit feed)"
+
msgid "Commits|History"
msgstr "過去更動 (commit) "
@@ -332,6 +359,9 @@ msgstr "無法刪除流水線 (pipeline) 排程"
msgid "Files"
msgstr "檔案"
+msgid "Filter by commit message"
+msgstr "以更動說明篩選"
+
msgid "Find by path"
msgstr "以路徑搜尋"
@@ -985,9 +1015,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 "查看此分支的合併請求 (merge request)"
+
msgid "VisibilityLevel|Internal"
msgstr "內部"
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/rubocop.rb b/rubocop/rubocop.rb
index 69b4b29507c..f76144275c9 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,9 +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/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 c6e5fb61cf9..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,24 +17,67 @@ 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
+ describe '#index' do
it 'shows group milestones page' do
get :index, group_id: group.to_param
expect(response).to have_http_status(200)
end
- it 'shows group milestones JSON' do
- get :index, group_id: group.to_param, format: :json
+ 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') }
- expect(response).to have_http_status(200)
- expect(response.content_type).to eq 'application/json'
+ 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
@@ -44,16 +87,57 @@ describe Groups::MilestonesController 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
- 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 = 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
+
+ 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
@@ -156,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
@@ -164,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
@@ -176,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/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 9db8ff5bbaa..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
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 48a2994cbc0..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(
- project_settings_members_path(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(
- project_settings_members_path(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(
- project_settings_members_path(project)
+ project_project_members_path(project)
)
expect(flash[:alert]).to eq('Please select a group.')
end
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/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index a8c44d5c313..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,104 +46,352 @@ 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 'DELETE #destroy' do
- set(:user) { create(:user) }
+ describe 'POST #create' do
+ describe 'functionality' do
+ set(:user) { create(:user) }
- context 'when a developer makes the request' do
before do
project.add_developer(user)
sign_in(user)
+ end
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ let(:basic_param) do
+ attributes_for(:ci_pipeline_schedule)
end
- it 'does not delete the pipeline schedule' do
- expect(response).not_to have_http_status(:ok)
+ 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
- context 'when a master makes the request' do
+ 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_master(user)
+ project.add_developer(user)
sign_in(user)
end
- it 'destroys the pipeline schedule' do
- expect do
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
- end.to change { project.pipeline_schedules.count }.by(-1)
+ 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
- expect(response).to have_http_status(302)
+ 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
- end
- describe 'security' do
- include AccessMatchersForController
+ describe 'security' do
+ let(:schedule) { { description: 'updated_desc' } }
- describe 'GET edit' 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) }
+ 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) }
- def go
+ 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 '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) }
+ 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
+ def go
+ get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
end
+ end
- describe 'PUT update' do
+ 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) }
+ 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
+
+ describe 'DELETE #destroy' do
+ set(:user) { create(:user) }
+
+ context 'when a developer makes the request' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
- def go
- put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id,
- schedule: { description: 'a' }
+ delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end
+
+ it 'does not delete the pipeline schedule' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'when a master makes the request' do
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'destroys the pipeline schedule' do
+ expect do
+ delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_http_status(302)
end
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 548ec8f487f..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 project_settings_members_path(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(project_settings_members_path(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(project_settings_members_path(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(
- project_settings_members_path(project)
+ project_project_members_path(project)
)
expect(project.members).not_to include member
end
@@ -253,7 +252,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- project_settings_members_path(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(
- project_settings_members_path(project)
+ project_project_members_path(project)
)
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/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index a0ecc756653..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(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
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/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index b88e801c3d7..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
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 063d54270bd..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
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 c6488cea798..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
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 59a50ff9264..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
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 5be0e2b2f17..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
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index f7d170a7bf6..011fdce21d8 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -15,7 +15,7 @@ describe 'Issues Feed', feature: true do
context 'when authenticated' do
it 'renders atom feed' do
- gitlab_sign_in user
+ sign_in user
visit project_issues_path(project, :atom)
expect(response_headers['Content-Type'])
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 3536d59bb08..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
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index eeb63f3f81a..d883b467c67 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -14,7 +14,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_board_path(project, board)
wait_for_requests
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 1f697581179..3d7e26c7e19 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -12,7 +12,7 @@ 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
@@ -519,7 +519,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'signed out user' do
before do
- gitlab_sign_out
+ sign_out(:user)
visit project_board_path(project, board)
wait_for_requests
end
@@ -542,8 +542,8 @@ describe 'Issue Boards', feature: true, js: true do
before do
project.team << [user_guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(user_guest)
+ 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 62693fb3d11..17b0da80947 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -15,7 +15,7 @@ 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
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index 34ae6c9d81d..8c16148023e 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -6,7 +6,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do
before do
create(:board, project: project)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit project_path(project)
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 40d1191a597..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
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index fa9d8b3f33d..6b267694201 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -10,7 +10,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_board_path(project, board)
wait_for_requests
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index f96ceffbc7d..fa17ef92bbb 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -20,7 +20,7 @@ describe 'Issue Boards', feature: true, js: true do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_board_path(project, board)
wait_for_requests
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index ddff4737563..f88bf237301 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -13,7 +13,7 @@ describe 'Sub-group project issue boards', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_board_path(project, board)
wait_for_requests
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/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 55ef1ef29bd..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: [])
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 25c5df56d57..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
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 7825d23c8f9..f530063352a 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -14,7 +14,7 @@ feature 'Cycle Analytics', feature: true, js: true do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_cycle_analytics_path(project)
wait_for_requests
@@ -38,7 +38,7 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(user)
+ sign_in(user)
visit project_cycle_analytics_path(project)
end
@@ -70,7 +70,7 @@ feature 'Cycle Analytics', feature: true, js: true do
user.update_attribute(:preferred_language, 'es')
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_cycle_analytics_path(project)
wait_for_requests
end
@@ -93,7 +93,7 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(guest)
+ 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/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 7d1fe2bd435..7ca002fc821 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -7,7 +7,13 @@ feature 'Dashboard Projects' do
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
@@ -71,6 +77,4 @@ feature 'Dashboard Projects' do
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/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 620184e2933..26d21207678 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -9,7 +9,7 @@ describe 'Discussion Comments Merge Request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_commit_path(project, sample_commit.id)
end
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index f90f82f8a48..11dbe10e1df 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -7,7 +7,7 @@ describe 'Discussion Comments Issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index 577d9c69bbc..db745be6fa1 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -7,7 +7,7 @@ describe 'Discussion Comments Merge Request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index a59be88db7d..eddbd4bde9b 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -7,7 +7,7 @@ describe 'Discussion Comments Issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_snippet_path(project, snippet)
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index f45752ab3f3..18c06a48111 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -10,7 +10,7 @@ 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)
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 5cd72e1d249..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
@@ -103,7 +103,7 @@ 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
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 c1dc7be7088..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,8 +128,8 @@ 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')
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 5046bfb5949..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
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 2c84965f7f3..823c779e0d9 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -12,7 +12,7 @@ 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
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index 62c5fce81b6..76cffc1d8c9 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -7,7 +7,7 @@ feature 'Issue awards', js: true, feature: true do
describe 'logged in' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
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 86226d97f79..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,7 +346,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'as a guest' do
before do
- gitlab_sign_in user
+ sign_in user
visit project_issues_path(project)
end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index f730141f82c..6e778f4d7e5 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -8,7 +8,7 @@ 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
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 3b7622882c1..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,7 +9,7 @@ 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
+ sign_in user
visit project_merge_request_path(project, merge_request)
end
@@ -82,7 +82,7 @@ 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
+ sign_in user
visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
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 97d49184920..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,7 +9,7 @@ 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
+ sign_in user
visit project_merge_request_path(project, merge_request)
end
@@ -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_project_issue_path(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 211f7eec560..2765d5448a4 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -23,7 +23,7 @@ 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 project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 364c5564a1c..98b1c5ee1b5 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -31,7 +31,7 @@ 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 project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 14c506eead3..fdc003f81b3 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -14,7 +14,7 @@ describe 'Dropdown hint', :js, :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
visit project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index 04d2b39dbf2..26a0320675f 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -34,7 +34,7 @@ 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 project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 1507e9f7616..7c74d8dffff 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -30,7 +30,7 @@ describe 'Dropdown milestone', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
visit project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 5b67d062f15..b16c5c280c7 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -9,7 +9,7 @@ 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 project_issues_path(project)
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 08360bfa641..a15c3d1d447 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -25,7 +25,7 @@ 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 project_issues_path(project)
@@ -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 5c75b0d56b0..05742004f06 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -15,7 +15,7 @@ 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
@@ -23,7 +23,7 @@ describe 'New/edit issue', :feature, :js do
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
@@ -63,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'
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index a0f26bf9a92..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)
+ 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/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 8d9bfcdf4e0..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
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index 396b53556bf..affba35f61c 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -6,7 +6,7 @@ feature 'Issue markdown toolbar', feature: true, js: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 568f2393aef..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
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 580b8d03fef..184cde5b9c5 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -27,7 +27,7 @@ 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)
+ sign_in(user)
visit project_issue_path(project, issue)
end
@@ -93,7 +93,7 @@ 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)
+ sign_in(user2)
visit project_issue_path(project, issue)
end
@@ -114,7 +114,7 @@ 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)
+ sign_in(user)
visit project_issue_path(project, issue)
end
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index 1871d853a90..6fb103e5477 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -9,7 +9,7 @@ describe 'Create notes on issues', :js, :feature do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
fill_in 'note[note]', with: note_text
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 76dae9212dd..39a458fe3d0 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -18,7 +18,7 @@ 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
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 1bcd717e8dd..f57b58f68e3 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -7,7 +7,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index ad28decfc00..1cd1f016674 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -13,7 +13,7 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
@@ -42,7 +42,7 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
+ sign_in(guest)
visit project_issue_path(project, issue)
end
@@ -82,7 +82,7 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
+ sign_in(guest)
visit project_issue_path(project, issue)
end
@@ -125,32 +125,6 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
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 project_issue_path(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 project_merge_request_path(project, merge_request)
- end
-
- it_behaves_like 'issuable time tracker'
- end
- end
-
describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) }
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 8cb62910e18..0016fa10f67 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -571,8 +571,6 @@ describe 'Issues', feature: true do
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_project_issue_path(project)
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
index 9d9a31ab8e8..985f42e484c 100644
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ b/spec/features/merge_requests/assign_issues_spec.rb
@@ -13,7 +13,7 @@ feature 'Merge request issue assignment', js: true, feature: true do
end
def visit_merge_request(current_user = nil)
- gitlab_sign_in(current_user || user)
+ sign_in(current_user || user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
index ed5a4fa5784..3b01c763281 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_requests/award_spec.rb
@@ -7,7 +7,7 @@ feature 'Merge request awards', js: true, feature: true do
describe 'logged in' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
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 0f8ab4cd92b..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
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb
index 5a5f884c6b3..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
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index 2f639b54637..527837b56be 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -20,7 +20,7 @@ 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 project_merge_request_path(project, merge_request)
wait_for_requests
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index a9947381f46..3e01ea69122 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -79,7 +79,7 @@ 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
@@ -164,7 +164,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 198fcba4e78..e0d97dec586 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -7,7 +7,7 @@ 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
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index 9f1b6be67d4..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
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index 671c17cd9e3..8d7160e2df2 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -8,7 +8,7 @@ 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 project_merge_request_path(merge_request.project, merge_request)
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 1b45bb73863..4fc70027193 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -20,7 +20,7 @@ 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
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 21dce185085..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
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 35976b615ad..d9de4e388d5 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -74,7 +74,7 @@ feature 'Diffs URL', js: true, feature: true do
context 'as author' do
it 'shows direct edit link' do
- gitlab_sign_in(author_user)
+ sign_in(author_user)
visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
@@ -84,7 +84,7 @@ 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)
+ sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index a50f66cfc64..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
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index 8ee78526232..b7063f35546 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -8,7 +8,7 @@ feature 'Edit Merge Request', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit edit_project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index e3d48128aeb..754f82900e4 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -26,7 +26,7 @@ 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 project_merge_requests_path(project)
end
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 2a62cda6d84..2a161b83aa0 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -14,7 +14,7 @@ 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 project_merge_requests_path(project)
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 8f2857c66f7..171386e16ad 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -16,7 +16,7 @@ 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
@@ -174,7 +174,7 @@ 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
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 831c60625f4..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,7 +34,7 @@ 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 project_merge_request_path(project, merge_request)
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index 716f829295e..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,7 +28,7 @@ feature 'Merge immediately', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
visit project_merge_request_path(merge_request.project, merge_request)
end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index 2a4178a819b..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
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 2c0632a4e82..4adf72a60b0 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -11,7 +11,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
before do
build.run
- gitlab_sign_in(user)
+ sign_in(user)
visit_merge_request
end
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 6bcfef71d25..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
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index d55e6329a9f..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
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
index c61f817dd9a..3ed76926eab 100644
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ b/spec/features/merge_requests/target_branch_spec.rb
@@ -10,7 +10,7 @@ describe 'Target branch', feature: true, js: true do
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 ae7e99d1462..912aa34b0c8 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
@@ -2,7 +2,7 @@ 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_project_merge_request_path(project, merge_request)
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
index 219b9fd8938..01251105f72 100644
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -8,7 +8,7 @@ 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 "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index f8f3e377198..43153e2cfa4 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -7,7 +7,7 @@ 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
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 7b1ac60231a..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' }
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index b3c8b0e9c34..35ed08e0a5e 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -13,7 +13,7 @@ describe 'Merge requests > User posts notes', :js do
end
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit project_merge_request_path(project, merge_request)
end
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 385708a28c5..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,7 +11,7 @@ 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
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 229dcda7ce4..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,7 +16,7 @@ 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)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
@@ -51,8 +51,8 @@ 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)
+ sign_out(:user)
+ sign_in(guest)
visit project_merge_request_path(project, merge_request)
end
@@ -97,8 +97,8 @@ 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)
+ sign_out(:user)
+ sign_in(guest)
visit project_merge_request_path(project, merge_request)
end
@@ -125,9 +125,9 @@ 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
@@ -181,8 +181,8 @@ 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)
+ sign_out(:user)
+ sign_in(guest)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 94fcfa398c9..218d57b49e3 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -8,7 +8,7 @@ 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
+ sign_in(create(:admin))
visit diffs_project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index c43c7460a08..b0fe5f3e1cb 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -12,7 +12,7 @@ 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 project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 8135411fe03..46c558659c7 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -7,7 +7,7 @@ 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
@@ -19,8 +19,7 @@ describe 'Merge request', :feature, :js do
target_project_id: project.id,
source_branch: 'feature',
target_branch: 'master'
- }
- )
+ })
end
it 'shows widget status after creating new merge request' do
@@ -206,8 +205,8 @@ 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 project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb
index 224723773bf..91cf8fc7218 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_requests/wip_message_spec.rb
@@ -6,7 +6,7 @@ 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
@@ -18,8 +18,7 @@ feature 'Work In Progress help message', feature: true do
target_project_id: project.id,
source_branch: 'wip',
target_branch: 'master'
- }
- )
+ })
within_wip_explanation do
expect(page).to have_text(
@@ -38,8 +37,7 @@ feature 'Work In Progress help message', feature: true do
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 880c53343bc..ce0c27cbe77 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,12 +1,14 @@
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
@@ -37,8 +39,8 @@ feature 'Milestone', feature: true do
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_project_milestone_path(project)
@@ -47,7 +49,20 @@ feature 'Milestone', feature: true do
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 fc7d2f9662d..626a1f35e62 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -9,7 +9,7 @@ describe 'Milestone show', feature: true do
before do
project.add_user(user, :developer)
- gitlab_sign_in(user)
+ sign_in(user)
end
def visit_milestone
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 a66d0f4abad..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
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 97925bc2ebf..b054f543dc6 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,6 +1,7 @@
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_project_path(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/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index efadb640096..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
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index cbd44c49d04..161d731f524 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -5,7 +5,7 @@ feature 'list of badges' do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_pipelines_settings_path(project)
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index c4e53293be0..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
@@ -92,7 +92,7 @@ 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
@@ -122,7 +122,7 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_edit_blob_path(project, tree_join(branch, file_path))
end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 52323c21112..f01860cc434 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -22,7 +22,7 @@ feature 'Download buttons in branches page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
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 ab9af8fa603..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,7 +8,7 @@ describe 'New Branch Ref Dropdown', :js, :feature do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit new_project_branch_path(project)
end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 69eeb8e285e..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
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index c8222326e91..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
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index 152c0d7c8de..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) { 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 c3adfa87c44..0f48751fa10 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -6,7 +6,7 @@ describe "Compare", js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_compare_index_path(project, from: "master", to: "master")
end
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index a310d14be10..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
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 290dc1a2f79..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
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
index 78c1a1f1d1a..1fca0dde534 100644
--- a/spec/features/projects/edit_spec.rb
+++ b/spec/features/projects/edit_spec.rb
@@ -6,7 +6,7 @@ feature 'Project edit', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_project_path(project)
end
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index 841514ac707..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
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index e3f40f8e661..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
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index af3af3eb965..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) { }
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 45b0c8d1a18..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" }
@@ -83,7 +83,7 @@ describe 'Edit Project Settings', feature: true do
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,7 +156,7 @@ describe 'Edit Project Settings', feature: true do
describe 'repository visibility', js: true do
before do
project.team << [member, :master]
- gitlab_sign_in(member)
+ sign_in(member)
visit edit_project_path(project)
end
@@ -242,7 +242,7 @@ describe 'Edit Project Settings', feature: true do
before do
project.team << [member, :guest]
- gitlab_sign_in(member)
+ sign_in(member)
visit project_path(project)
end
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index ac2b926f4de..d9a561b23a2 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -6,7 +6,7 @@ feature 'user browses project', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_tree_path(project, project.default_branch)
end
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
index 1196994ac3a..55350db4c34 100644
--- a/spec/features/projects/files/creating_a_file_spec.rb
+++ b/spec/features/projects/files/creating_a_file_spec.rb
@@ -6,7 +6,7 @@ feature 'User wants to create a file', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_new_blob_path(project, project.default_branch)
end
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 783d98dafa7..0cd0c9addd0 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -7,7 +7,7 @@ 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 project_new_blob_path(project, 'master', file_name: 'Dockerfile')
end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 4f4fab8a6e5..a2874483149 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -22,7 +22,7 @@ feature 'Download buttons in files tree', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
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 83aea070901..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,7 +5,7 @@ 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
+ sign_in user
visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
editor = find('.file-editor.code')
editor.click
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index c9b0dbd0ffb..c295380dfc9 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -17,7 +17,7 @@ feature 'User wants to edit a file', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in user
+ 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 07b4aa80f4b..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,7 +6,7 @@ feature 'User views files page', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_tree_path(project, project.repository.root_ref)
end
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index 087eef5d407..772f81c8853 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -6,7 +6,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_find_file_path(project, project.repository.root_ref)
diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb
index d2ccc9a0732..7a99596585f 100644
--- a/spec/features/projects/files/find_files_spec.rb
+++ b/spec/features/projects/files/find_files_spec.rb
@@ -5,7 +5,7 @@ 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
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 23c145d0a11..a3a7b08c013 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -5,7 +5,7 @@ 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
+ sign_in user
visit project_new_blob_path(project, 'master', file_name: '.gitignore')
end
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 0539b77e3dd..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,7 +5,7 @@ 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
+ sign_in user
visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml')
end
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 3a4ed3d8cf0..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,7 +7,7 @@ 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)
+ sign_in(project_master)
visit project_path(project)
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 77f97826427..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,7 +5,7 @@ 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
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 53b673538e5..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
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index e18ff42942f..d50ddb1f1a9 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -6,7 +6,7 @@ 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
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index a8661ad4d24..aa2306069ad 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -4,7 +4,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do
let(:project) { create(:project) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit project_path(project)
end
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index 64415ffe57f..631955a60a1 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -9,7 +9,7 @@ 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
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index f6e24a0aa31..1c5f89fa898 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -7,7 +7,7 @@ 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
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index b7f0ad9197e..43c2c401f4a 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -33,7 +33,7 @@ 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
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 3f8d2255298..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'
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 f12b28f05fc..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
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 83d1dfd91a9..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
@@ -124,11 +124,14 @@ 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',
diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb
index 380ade24a32..c2ca62508a4 100644
--- a/spec/features/projects/issues/list_spec.rb
+++ b/spec/features/projects/issues/list_spec.rb
@@ -7,7 +7,7 @@ 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
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index d68606ab545..d274a1760a4 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -9,10 +9,11 @@ feature 'Project Issues RSS' do
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 e52151e9585..411987573fa 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -16,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
@@ -391,8 +391,8 @@ feature 'Jobs', :feature do
job.cancel!
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- gitlab_sign_out_direct
- gitlab_sign_in(create(:user))
+ sign_out(:user)
+ sign_in(create(:user))
visit project_job_path(project, job)
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 89e31a72869..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,7 +28,7 @@ feature 'Issue prioritization', feature: true do
issue_2.labels << label_4
issue_1.labels << label_5
- gitlab_sign_in user
+ sign_in user
visit project_issues_path(project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
@@ -67,7 +67,7 @@ feature 'Issue prioritization', feature: true do
issue_4.labels << label_4 # 7
issue_6.labels << label_5 # 8 - No priority
- gitlab_sign_in user
+ 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 04617bfe03e..58421e11e0a 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -10,7 +10,7 @@ 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
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 034613ea6be..61f6d734ed3 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -14,7 +14,7 @@ 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
@@ -120,7 +120,7 @@ feature 'Prioritize labels', feature: true do
it 'does not prioritize labels' do
guest = create(:user)
- gitlab_sign_in guest
+ sign_in guest
visit project_labels_path(project)
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index b14e0f089f0..8b952d2f3a5 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -22,7 +22,7 @@ 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
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index 5f48253dd06..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) { 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 4958d5594ac..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 project_settings_members_path(project)
+ visit project_project_members_path(project)
expect(current_path).to eq(
- project_settings_members_path(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 61cd7db15f5..b9154915b34 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -9,7 +9,7 @@ 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)
+ sign_in(user)
visit project_settings_members_path(project)
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 1c429202aba..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,7 +7,7 @@ feature 'Projects > Members > Group member cannot leave group project', feature:
background do
group.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_path(project)
end
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 7250a0d26fc..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)
+ 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 0acf5134cce..bfc604bb8d6 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -13,7 +13,7 @@ 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
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 5a28a7538f8..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,7 +8,7 @@ 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 project_path(project)
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index b62bf2f6293..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
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 ca2172bb905..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,7 +10,7 @@ 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
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 69c5927428c..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,7 +8,7 @@ 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
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 f0da201da85..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,7 +6,7 @@ feature 'Projects > Members > Member cannot request access to his project', feat
background do
project.team << [member, :developer]
- gitlab_sign_in(member)
+ sign_in(member)
visit project_path(project)
end
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index 31d8bbdc0b6..3de13aee0ee 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -6,7 +6,7 @@ feature 'Projects > Members > Member leaves project', feature: true do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_path(project)
end
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 a1ccc6ddf65..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,7 +4,7 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
+ sign_in(project.owner)
visit project_path(project)
end
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 54f5d0d165b..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,7 +4,7 @@ feature 'Projects > Members > Owner cannot request access to his project', featu
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
+ sign_in(project.owner)
visit project_path(project)
end
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index 7c02b49a0ab..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
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 247cc0e6f2c..ab86e2da4f6 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -6,7 +6,7 @@ feature 'Projects > Members > User requests access', feature: true do
let(:master) { project.owner }
background do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_path(project)
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 project_settings_members_path(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 771dd7d3208..12b4747602d 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -18,7 +18,7 @@ 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
@@ -51,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
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
index ff4d22b3881..6548b4b83e6 100644
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ b/spec/features/projects/merge_requests/list_spec.rb
@@ -7,7 +7,7 @@ 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
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index 1913ef728d3..642ca7448a3 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -6,7 +6,7 @@ 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
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index 1b74758445b..53cd2711666 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -15,7 +15,7 @@ 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
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index e2cc38e276f..a8593709f1b 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -10,7 +10,7 @@ feature 'Pages', feature: true do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
shared_examples 'no pages deployed' do
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index d8bb7ca9a83..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,6 +129,72 @@ 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_project_pipeline_schedule_path(project, pipeline_schedule)
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index bd6750d2208..4a08d9088aa 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -5,7 +5,7 @@ describe 'Pipeline', :feature, :js do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index a82a804e4c1..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
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index 1f78f242399..89d227eb98f 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -7,7 +7,7 @@ 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
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 342f083f25a..31c7b492ab7 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -6,7 +6,7 @@ feature 'Ref switcher', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_tree_path(project, 'master')
end
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
index 9e4f420689c..7c29af247d6 100644
--- a/spec/features/projects/services/jira_service_spec.rb
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -24,7 +24,7 @@ feature 'Setup Jira service', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_settings_integrations_path(project)
end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index aaa354903aa..584d3ed8f42 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -9,7 +9,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
before do
stub_mattermost_setting(enabled: mattermost_enabled)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_project_service_path(project, service)
end
diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb
index 5e3c3b00476..709cd1226c3 100644
--- a/spec/features/projects/services/slack_service_spec.rb
+++ b/spec/features/projects/services/slack_service_spec.rb
@@ -9,7 +9,7 @@ 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
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index aaa775ce51f..4efe484262a 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -7,7 +7,7 @@ feature 'Slack slash commands', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_project_service_path(project, service)
end
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index f708a3009f1..13313bfde24 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -7,7 +7,7 @@ feature 'Integration settings', feature: true do
let(:integrations_path) { project_settings_integrations_path(project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index 451e2f3e04e..ecaf65c4ad9 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -6,7 +6,7 @@ feature 'Project settings > Merge Requests', feature: true, js: true do
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 0d78feb2b93..724cfa10e72 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -6,7 +6,7 @@ feature "Pipelines settings", feature: true do
let(:role) { :developer }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
visit project_pipelines_settings_path(project)
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 9cc04925a0a..98539518f6c 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -7,7 +7,7 @@ feature 'Repository settings', feature: true do
background do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'for developer' do
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index a9a6441d4e8..32d8f1fd16a 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -6,7 +6,7 @@ feature 'Visibility settings', feature: true, js: true do
context 'as owner' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_project_path(project)
end
@@ -32,7 +32,7 @@ feature 'Visibility settings', feature: true, js: true do
before do
project.team << [master_user, :master]
- gitlab_sign_in(master_user)
+ sign_in(master_user)
visit edit_project_path(project)
end
diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index 682bea87c8a..8dd70e07b30 100644
--- a/spec/features/projects/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
@@ -7,7 +7,7 @@ feature 'Project shortcuts', feature: true do
describe 'On a project', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_path(project)
end
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 37c11c0e88d..06d32423a13 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -17,7 +17,7 @@ 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 project_snippets_path(project)
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index d401d09497f..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
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
index 8edef2eba13..513a05151b2 100644
--- a/spec/features/projects/snippets_spec.rb
+++ b/spec/features/projects/snippets_spec.rb
@@ -29,7 +29,7 @@ describe 'Project snippets', :js, feature: true do
context 'when submitting a note' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit project_snippet_path(project, snippets[0])
end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 5bbad78d0bb..007910bb931 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -8,7 +8,7 @@ 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
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 186876e454f..34c5e59c3e5 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -23,7 +23,7 @@ feature 'Download buttons in tags page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 4583374c931..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) { 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 01f288934bf..5e302da8a63 100644
--- a/spec/features/projects/user_create_dir_spec.rb
+++ b/spec/features/projects/user_create_dir_spec.rb
@@ -6,7 +6,7 @@ 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 project_tree_path(project, 'master')
open_new_directory_modal
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index 0c06aa25c06..2a316a0d0db 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -50,7 +50,7 @@ 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_project_merge_request_path(project, merge_request)
@@ -66,7 +66,7 @@ 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 project_compare_path(project, from: 'master', to: branch_name)
@@ -80,7 +80,7 @@ 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 project_compare_path(project, from: 'master', to: sha)
@@ -94,7 +94,7 @@ 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 project_blob_path(project, File.join(branch_name, file_path))
@@ -108,7 +108,7 @@ 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 project_blob_path(project, File.join(sha, file_path))
@@ -122,7 +122,7 @@ describe 'View on environment', js: true do
context 'when visiting the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_commit_path(project, sha)
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index d79ab809c7d..231e8eed4fb 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -16,7 +16,7 @@ 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 project_path(project)
find('.shortcuts-wiki').trigger('click')
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index d189f84da0e..ea816082479 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -8,7 +8,7 @@ feature 'Wiki shortcuts', :feature, :js do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_wiki_path(project, wiki_page)
end
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 86b31057a55..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 project_path(project)
- find('.shortcuts-wiki').trigger('click')
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 749721b97eb..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,7 +13,7 @@ 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
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 3b9f7ff96fb..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,7 +6,7 @@ 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 project_wikis_path(project)
end
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 8e3912d994e..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,
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 a305d27c7ec..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
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 361e3a6d8e5..10c7e5934e4 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -6,7 +6,7 @@ feature 'Project', feature: true do
let(:path) { project_path(project) }
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
it 'parses Markdown' do
@@ -39,7 +39,7 @@ 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_project_path(project)
end
@@ -60,7 +60,7 @@ 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_project_path(project)
end
@@ -79,7 +79,7 @@ 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 project_path(project)
end
@@ -98,7 +98,7 @@ 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 project_issue_path(project, issue)
@@ -123,7 +123,7 @@ feature 'Project', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit project_path(project)
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 952eb6c3643..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)
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 4ffd97fb221..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)
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 2486f779753..d82ebe02f77 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -8,7 +8,7 @@ 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
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index d283c2d3c8f..cb1cb1a1417 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -8,7 +8,7 @@ describe 'Reportable note on issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index fe25c894b85..8a531b9a9e9 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -7,7 +7,7 @@ describe 'Reportable note on merge request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index b3044d3d048..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
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 00f59f8f197..1725b70acf3 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -4,7 +4,7 @@ describe "Runners" do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "specific runners" do
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 69b42193955..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
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/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 3bf5544a837..1cef3d5c6f4 100644
--- a/spec/features/tags/master_creates_tag_spec.rb
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -6,7 +6,7 @@ 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
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 04f9cecd46d..98af1d6b4f7 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -6,7 +6,7 @@ feature 'Master deletes tag', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_tags_path(project)
end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
index 092ffbb6d23..1b61fde7227 100644
--- a/spec/features/tags/master_updates_tag_spec.rb
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -6,7 +6,7 @@ feature 'Master updates tag', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit project_tags_path(project)
end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
index b1f3207eeea..fb910feae34 100644
--- a/spec/features/tags/master_views_tags_spec.rb
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -5,7 +5,7 @@ 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
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 736178897a6..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,7 +8,7 @@ feature 'User uploads file to note', feature: true do
let(:issue) { create(:issue, project: project, author: user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit project_issue_path(project, issue)
end
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 24fff1a3052..670e8dda916 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -33,22 +33,24 @@ describe 'User can display performance bar', :js do
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 performance 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 1a2dedf27eb..dd770fe5043 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -6,7 +6,7 @@ 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
@@ -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 4a52f0d5c58..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 } }
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/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 7a522487a74..717ac1962d1 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -2,12 +2,6 @@ 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 project_project_members_url(project) }
- end
-
describe '#project_member_path' do
let(:project_member) { create(:project_member) }
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/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/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/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 49ef21f75de..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';
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 56d938e1fbe..b69f4eddffc 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -2481,6 +2481,7 @@ export const singleRowMetrics = [
'queries': [
{
'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+ 'label': 'Container CPU',
'result': [
{
'metric': {
diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js
index b3bc97adc9f..c423024dce0 100644
--- a/spec/javascripts/monitoring/monitoring_column_spec.js
+++ b/spec/javascripts/monitoring/monitoring_column_spec.js
@@ -95,7 +95,7 @@ describe('MonitoringColumn', () => {
});
});
- it('has a title for the y-axis that comes from the backend', () => {
+ 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',
@@ -104,5 +104,6 @@ describe('MonitoringColumn', () => {
});
expect(component.yAxisLabel).toEqual(component.columnData.y_label);
+ expect(component.legendTitle).toEqual(component.columnData.queries[0].label);
});
});
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/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 482f03aa0cc..ef58ef1b0cd 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -878,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
@@ -896,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
@@ -915,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/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/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/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 6aca181194a..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
@@ -35,16 +49,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.root_ref
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 '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)
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :default_branch_name do
+ subject { repository.root_ref }
end
end
@@ -130,17 +136,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
subject
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 '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_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :branch_names
end
describe '#tag_names' do
@@ -168,17 +164,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
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_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :tag_names
end
shared_examples 'archive check' do |extenstion|
@@ -438,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
@@ -1298,16 +1297,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
@repo.local_branches
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 '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)
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :local_branches do
+ subject { @repo.local_branches }
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/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/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/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/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/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/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 166a4474abf..fb485d0b2c6 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -214,6 +214,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 7de5e2e3920..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
@@ -1183,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 }
@@ -1355,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) }
@@ -1373,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
@@ -1473,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' } }
@@ -1485,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 55d85a6e228..ba0696fa210 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -748,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 50f7c029af8..890ffaae494 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -3,15 +3,10 @@ require 'spec_helper'
describe Ci::Variable, models: true do
subject { build(:ci_variable) }
- let(:secret_value) { 'secret' }
-
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) }
- 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) }
end
describe '.unprotected' do
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/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/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 9e37c2b20c4..21893e0cbaa 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -13,15 +13,34 @@ describe ShaAttribute do
end
describe '#sha_attribute' do
- it 'defines a SHA attribute for a binary column' do
- expect(model).to receive(:attribute)
- .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+ context 'when the table exists' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(true)
+ end
- model.sha_attribute(:sha1)
+ 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
- it 'raises ArgumentError when the column type is not :binary' do
- expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+ 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/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 5c13cf584f9..38fbdd2536a 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -42,7 +42,7 @@ describe ForkedProjectLink, "add link on fork" do
describe '#forked?' do
let(:project_to) { create(:project, forked_project_link: forked_project_link) }
- let(:forked_project_link) { build(:forked_project_link) }
+ let(:forked_project_link) { create(:forked_project_link) }
before do
forked_project_link.forked_from_project = project_from
@@ -59,9 +59,9 @@ describe ForkedProjectLink, "add link on fork" do
end
it "project_to.destroy destroys fork_link" do
- expect(forked_project_link).to receive(:destroy)
-
project_to.destroy
+
+ expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
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 587d4b83cb4..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
@@ -720,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
@@ -752,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,
@@ -794,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
@@ -834,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
@@ -842,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/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 4a1de76f099..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 }
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_spec.rb b/spec/models/project_spec.rb
index fd120a8024a..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) }
@@ -1504,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)) }
@@ -1849,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
@@ -1858,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
@@ -2173,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/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/features_spec.rb b/spec/requests/api/features_spec.rb
index 1d8aaeea8f2..7e21006b254 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -113,6 +113,20 @@ describe API::Features do
{ '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
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/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 14dec3d45b1..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)
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 70b94a09e6b..c34b88f0741 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -163,6 +163,35 @@ 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
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/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/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/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/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 35373675894..a2db3f68ff7 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -431,22 +431,6 @@ describe QuickActions::InterpretService, services: true do
end
end
- context 'reassign command' do
- let(:content) { '/reassign' }
-
- context 'Issue' do
- it 'reassigns user if content contains /reassign @user' do
- user = create(:user)
-
- issue.update(assignee_ids: [developer.id])
-
- _, updates = service.execute("/reassign @#{user.username}", issue)
-
- expect(updates).to eq(assignee_ids: [user.id])
- end
- end
- end
-
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index e35e4c1d631..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) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3e90a642d56..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 Gitlab::Routing.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/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/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 98b014df6cd..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
@@ -110,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
@@ -145,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
@@ -175,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/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/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/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
index fb43f51c70c..ff60bd0c0ae 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -50,9 +50,24 @@ module AccessMatchersForController
"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|
- emulate_user(role, @membership)
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
action.call
EXPECTED_STATUS_CODE_ALLOWED.include?(response.status)
@@ -62,13 +77,18 @@ module AccessMatchersForController
@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|
- emulate_user(role, @membership)
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
action.call
EXPECTED_STATUS_CODE_DENIED.include?(response.status)
@@ -78,6 +98,10 @@ module AccessMatchersForController
@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
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/test_env.rb b/spec/support/test_env.rb
index 32546abcad4..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
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