summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml57
-rw-r--r--.gitlab/issue_templates/Bug.md24
-rw-r--r--.rubocop.yml22
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock8
-rw-r--r--LICENSE2
-rw-r--r--PROCESS.md38
-rw-r--r--app/assets/javascripts/application.js3
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es61
-rw-r--r--app/assets/javascripts/boards/filters/due_date_filters.js.es63
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_store.js.es62
-rw-r--r--app/assets/javascripts/copy_as_gfm.js.es63
-rw-r--r--app/assets/javascripts/diff.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es611
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es611
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js.es619
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_count.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es613
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js.es64
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js.es648
-rw-r--r--app/assets/javascripts/dispatcher.js.es61
-rw-r--r--app/assets/javascripts/due_date_select.js.es659
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es62
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es67
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js.es62
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/issuable_form.js16
-rw-r--r--app/assets/javascripts/label_manager.js.es626
-rw-r--r--app/assets/javascripts/member_expiration_date.js.es633
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es62
-rw-r--r--app/assets/javascripts/milestone.js160
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js.es62
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/single_file_diff.js4
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js (renamed from app/assets/javascripts/boards/test_utils/simulate_drag.js)5
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js.es611
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es66
-rw-r--r--app/assets/stylesheets/application.scss3
-rw-r--r--app/assets/stylesheets/framework/calendar.scss55
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss124
-rw-r--r--app/assets/stylesheets/framework/files.scss43
-rw-r--r--app/assets/stylesheets/framework/jquery.scss57
-rw-r--r--app/assets/stylesheets/pages/diff.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss13
-rw-r--r--app/assets/stylesheets/pages/issues.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss16
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss15
-rw-r--r--app/assets/stylesheets/pages/milestone.scss6
-rw-r--r--app/assets/stylesheets/pages/profile.scss4
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/admin/groups_controller.rb4
-rw-r--r--app/controllers/dashboard/groups_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb35
-rw-r--r--app/controllers/projects/lfs_api_controller.rb4
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/protected_branches_controller.rb8
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/finders/groups_finder.rb2
-rw-r--r--app/finders/projects_finder.rb2
-rw-r--r--app/helpers/merge_requests_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/routable.rb66
-rw-r--r--app/models/merge_request.rb10
-rw-r--r--app/models/namespace.rb40
-rw-r--r--app/models/project.rb43
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/route.rb27
-rw-r--r--app/models/user.rb2
-rw-r--r--app/serializers/environment_serializer.rb47
-rw-r--r--app/serializers/pipeline_serializer.rb40
-rw-r--r--app/services/delete_user_service.rb31
-rw-r--r--app/services/destroy_group_service.rb29
-rw-r--r--app/services/groups/destroy_service.rb25
-rw-r--r--app/services/notes/destroy_service.rb (renamed from app/services/notes/delete_service.rb)2
-rw-r--r--app/services/users/destroy_service.rb33
-rw-r--r--app/views/admin/logs/show.html.haml2
-rw-r--r--app/views/ci/lints/show.html.haml2
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml2
-rw-r--r--app/views/discussions/_resolve_all.html.haml3
-rw-r--r--app/views/groups/_home_panel.html.haml17
-rw-r--r--app/views/groups/_show_nav.html.haml7
-rw-r--r--app/views/groups/milestones/show.html.haml4
-rw-r--r--app/views/groups/show.html.haml43
-rw-r--r--app/views/groups/subgroups.html.haml20
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml16
-rw-r--r--app/views/projects/_last_push.html.haml1
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/boards/_show.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml4
-rw-r--r--app/views/projects/compare/_form.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/diffs/_file_header.html.haml6
-rw-r--r--app/views/projects/issues/_discussion.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml107
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--app/views/projects/labels/index.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml119
-rw-r--r--app/views/projects/merge_requests/_show.html.haml3
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml3
-rw-r--r--app/views/projects/notes/_note.html.haml3
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/shared/_group_form.html.haml7
-rw-r--r--app/views/shared/groups/_group.html.haml6
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml3
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/projects/_dropdown.html.haml12
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/sherlock/file_samples/show.html.haml2
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/workers/delete_user_worker.rb2
-rw-r--r--app/workers/group_destroy_worker.rb2
-rw-r--r--changelogs/unreleased/20495-plus-icon-button.yml4
-rw-r--r--changelogs/unreleased/23104-remove-public-param-for-projects.yml4
-rw-r--r--changelogs/unreleased/25709-diff-file-overflow.yml4
-rw-r--r--changelogs/unreleased/26705-filter-todos-by-manual-add.yml4
-rw-r--r--changelogs/unreleased/27610-issue-number-alignment.yml4
-rw-r--r--changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml4
-rw-r--r--changelogs/unreleased/27880-pipelines-table-not-showing-commit-branch.yml4
-rw-r--r--changelogs/unreleased/27943-contribution-list-on-profile-page-is-aligned-right.yml4
-rw-r--r--changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml4
-rw-r--r--changelogs/unreleased/copy-branch-to-clipboard.yml4
-rw-r--r--changelogs/unreleased/dz-create-nested-groups-via-ui.yml4
-rw-r--r--changelogs/unreleased/dz-nested-groups-api.yml4
-rw-r--r--changelogs/unreleased/dz-refactor-full-path.yml4
-rw-r--r--changelogs/unreleased/fixes-namespace-api-documentation.yml4
-rw-r--r--changelogs/unreleased/issue_26701.yml4
-rw-r--r--changelogs/unreleased/lfs-noauth-public-repo.yml4
-rw-r--r--changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml4
-rw-r--r--changelogs/unreleased/protected-branch-dropdown-titles.yml4
-rw-r--r--changelogs/unreleased/removal_of_unused_parameter.yml4
-rw-r--r--changelogs/unreleased/remove-jquery-ui-datepicker.yml4
-rw-r--r--changelogs/unreleased/remove-jquery-ui-sortable.yml4
-rw-r--r--changelogs/unreleased/rename_delete_services.yml4
-rw-r--r--changelogs/unreleased/upgrade-babel-v6.yml4
-rw-r--r--changelogs/unreleased/upgrade-webpack-v2-2.yml4
-rw-r--r--config/dependency_decisions.yml18
-rw-r--r--config/routes/group.rb1
-rw-r--r--config/webpack.config.js33
-rw-r--r--db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb8
-rw-r--r--db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb6
-rw-r--r--db/migrate/20170204172458_add_name_to_route.rb12
-rw-r--r--db/schema.rb1
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/monitoring/performance/introduction.md2
-rw-r--r--doc/administration/monitoring/performance/prometheus.md103
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/index.md147
-rw-r--r--doc/administration/monitoring/prometheus/node_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/postgres_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/redis_exporter.md33
-rw-r--r--doc/api/groups.md60
-rw-r--r--doc/api/oauth2.md6
-rw-r--r--doc/api/projects.md48
-rw-r--r--doc/api/repository_files.md2
-rw-r--r--doc/gitlab-basics/command-line-commands.md2
-rw-r--r--doc/gitlab-basics/img/profile_settings.pngbin3045 -> 5842 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.pngbin8133 -> 24639 bytes
-rw-r--r--doc/integration/auth0.md7
-rw-r--r--doc/integration/azure.md6
-rw-r--r--doc/integration/cas.md9
-rw-r--r--doc/integration/crowd.md9
-rw-r--r--doc/integration/facebook.md6
-rw-r--r--doc/integration/github.md22
-rw-r--r--doc/integration/gitlab.md16
-rw-r--r--doc/integration/google.md6
-rw-r--r--doc/integration/saml.md6
-rw-r--r--doc/integration/shibboleth.md12
-rw-r--r--doc/integration/twitter.md6
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md24
-rw-r--r--doc/user/markdown.md35
-rw-r--r--features/snippets/user.feature34
-rw-r--r--features/steps/snippets/user.rb55
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/groups.rb3
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/projects.rb19
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/serializer/ci/variables.rb (renamed from lib/gitlab/serialize/ci/variables.rb)2
-rw-r--r--lib/gitlab/serializer/pagination.rb36
-rw-r--r--package.json20
-rw-r--r--rubocop/cop/gem_fetcher.rb23
-rw-r--r--rubocop/cop/migration/add_column.rb (renamed from rubocop/cop/migration/column_with_default.rb)8
-rw-r--r--rubocop/cop/migration/add_column_with_default.rb34
-rw-r--r--rubocop/cop/migration/add_index.rb4
-rw-r--r--rubocop/rubocop.rb6
-rw-r--r--spec/factories/todos.rb4
-rw-r--r--spec/features/boards/boards_spec.rb28
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb28
-rw-r--r--spec/features/groups_spec.rb19
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb22
-rw-r--r--spec/features/issues_spec.rb18
-rw-r--r--spec/features/merge_requests/widget_spec.rb2
-rw-r--r--spec/features/milestones/milestones_spec.rb5
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb4
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb3
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb16
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb4
-rw-r--r--spec/features/protected_branches/access_control_ce_spec.rb12
-rw-r--r--spec/features/snippets/user_snippets_spec.rb49
-rw-r--r--spec/features/todos/todos_filtering_spec.rb57
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb82
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_store_spec.js.es63
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js.es64
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb (renamed from spec/lib/gitlab/serialize/ci/variables_spec.rb)2
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb49
-rw-r--r--spec/models/concerns/routable_spec.rb34
-rw-r--r--spec/models/merge_request_spec.rb6
-rw-r--r--spec/models/namespace_spec.rb49
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb21
-rw-r--r--spec/models/route_spec.rb45
-rw-r--r--spec/models/user_spec.rb10
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/projects_spec.rb61
-rw-r--r--spec/requests/lfs_http_spec.rb1
-rw-r--r--spec/rubocop/cop/gem_fetcher_spec.rb46
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_spec.rb41
-rw-r--r--spec/serializers/environment_serializer_spec.rb132
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb6
-rw-r--r--spec/services/groups/destroy_service_spec.rb (renamed from spec/services/destroy_group_service_spec.rb)16
-rw-r--r--spec/services/notes/destroy_service_spec.rb (renamed from spec/services/notes/delete_service_spec.rb)2
-rw-r--r--spec/services/users/destroy_spec.rb (renamed from spec/services/delete_user_service_spec.rb)11
-rw-r--r--spec/support/drag_to_helper.rb13
-rw-r--r--spec/workers/delete_user_worker_spec.rb4
239 files changed, 2440 insertions, 1474 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5ab3648d9f2..733710bb005 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -163,64 +163,7 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack
-# Execute all testing suites against Ruby 2.1
-.ruby-21: &ruby-21
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
- <<: *use-db
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
- cache:
- key: "ruby21"
- paths:
- - vendor/ruby
-
-.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
- <<: *rspec-knapsack
- <<: *dedicated-runner
- <<: *ruby-21
-
-.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
- <<: *spinach-knapsack
- <<: *dedicated-runner
- <<: *ruby-21
-
-rspec 0 20 ruby21: *rspec-knapsack-ruby21
-rspec 1 20 ruby21: *rspec-knapsack-ruby21
-rspec 2 20 ruby21: *rspec-knapsack-ruby21
-rspec 3 20 ruby21: *rspec-knapsack-ruby21
-rspec 4 20 ruby21: *rspec-knapsack-ruby21
-rspec 5 20 ruby21: *rspec-knapsack-ruby21
-rspec 6 20 ruby21: *rspec-knapsack-ruby21
-rspec 7 20 ruby21: *rspec-knapsack-ruby21
-rspec 8 20 ruby21: *rspec-knapsack-ruby21
-rspec 9 20 ruby21: *rspec-knapsack-ruby21
-rspec 10 20 ruby21: *rspec-knapsack-ruby21
-rspec 11 20 ruby21: *rspec-knapsack-ruby21
-rspec 12 20 ruby21: *rspec-knapsack-ruby21
-rspec 13 20 ruby21: *rspec-knapsack-ruby21
-rspec 14 20 ruby21: *rspec-knapsack-ruby21
-rspec 15 20 ruby21: *rspec-knapsack-ruby21
-rspec 16 20 ruby21: *rspec-knapsack-ruby21
-rspec 17 20 ruby21: *rspec-knapsack-ruby21
-rspec 18 20 ruby21: *rspec-knapsack-ruby21
-rspec 19 20 ruby21: *rspec-knapsack-ruby21
-
-spinach 0 10 ruby21: *spinach-knapsack-ruby21
-spinach 1 10 ruby21: *spinach-knapsack-ruby21
-spinach 2 10 ruby21: *spinach-knapsack-ruby21
-spinach 3 10 ruby21: *spinach-knapsack-ruby21
-spinach 4 10 ruby21: *spinach-knapsack-ruby21
-spinach 5 10 ruby21: *spinach-knapsack-ruby21
-spinach 6 10 ruby21: *spinach-knapsack-ruby21
-spinach 7 10 ruby21: *spinach-knapsack-ruby21
-spinach 8 10 ruby21: *spinach-knapsack-ruby21
-spinach 9 10 ruby21: *spinach-knapsack-ruby21
-
# Other generic tests
-
.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 6d7d88c6791..34c2e097ba8 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -6,13 +6,13 @@
(How one can reproduce the issue - this is very important)
-### Expected behavior
+### What is the current *bug* behavior?
-(What you should see instead)
+(What actually happens)
-### Actual behavior
+### What is the expected *correct* behavior?
-(What actually happens)
+(What you should see instead)
### Relevant logs and/or screenshots
@@ -23,23 +23,23 @@ logs, and code as it's very hard to read otherwise.)
(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)
-#### Results of GitLab application Check
+#### Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
-`sudo gitlab-rake gitlab:check SANITIZE=true`)
+`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
-`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
-
-(we will only investigate if the tests are passing)
+`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
-#### Results of GitLab environment info
+#### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of:
-`sudo gitlab-rake gitlab:env:info`)
+`sudo gitlab-rake gitlab:check SANITIZE=true`)
(For installations from source run and paste the output of:
-`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
+
+(we will only investigate if the tests are passing)
### Possible fixes
diff --git a/.rubocop.yml b/.rubocop.yml
index cfff42e5c99..88345373a5b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -31,8 +31,7 @@ AllCops:
- 'lib/gitlab/seeder.rb'
- 'generator_templates/**/*'
-
-##################### Style ##################################
+# Style #######################################################################
# Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation:
@@ -471,7 +470,7 @@ Style/WhileUntilModifier:
Style/WordArray:
Enabled: false
-#################### Metrics ################################
+# Metrics #####################################################################
# A calculated magnitude based on number of assignments,
# branches, and conditions.
@@ -516,8 +515,7 @@ Metrics/PerceivedComplexity:
Enabled: true
Max: 18
-
-#################### Lint ################################
+# Lint ########################################################################
# Checks for useless access modifiers.
Lint/UselessAccessModifier:
@@ -679,8 +677,7 @@ Lint/UselessSetterCall:
Lint/Void:
Enabled: true
-
-##################### Performance ############################
+# Performance #################################################################
# Use `casecmp` rather than `downcase ==`.
Performance/Casecmp:
@@ -718,8 +715,7 @@ Performance/StringReplacement:
Performance/TimesMap:
Enabled: true
-
-##################### Rails ##################################
+# Rails #######################################################################
# Enables Rails cops.
Rails:
@@ -767,7 +763,7 @@ Rails/ReadWriteAttribute:
Rails/ScopeArgs:
Enabled: true
-##################### RSpec ##################################
+# RSpec #######################################################################
# Check that instances are not being stubbed globally.
RSpec/AnyInstance:
@@ -828,3 +824,9 @@ RSpec/NotToNot:
# Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles:
Enabled: false
+
+# Custom ######################################################################
+
+# Disallow the `git` and `github` arguments in the Gemfile.
+GemFetcher:
+ Enabled: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 315cd1e598c..72cd57ad7ff 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,7 +15,7 @@
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- - [Stewardship][#stewardship]
+ - [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
@@ -430,7 +430,7 @@ merge request:
1. [Newlines styleguide][newlines-styleguide]
1. [Testing](doc/development/testing.md)
1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
-1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
+1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index abd410582de..0d91a54c7d4 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.2.4
+0.3.0
diff --git a/Gemfile b/Gemfile
index 4aff9cd396c..79433b12823 100644
--- a/Gemfile
+++ b/Gemfile
@@ -284,7 +284,7 @@ group :development, :test do
gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
- gem 'rspec_profiling'
+ gem 'rspec_profiling', '~> 0.0.5'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index c5dd6ab8d22..235426afa49 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -638,7 +638,7 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.5.0)
- rspec_profiling (0.0.4)
+ rspec_profiling (0.0.5)
activerecord
pg
rails
@@ -738,7 +738,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sqlite3 (1.3.11)
+ sqlite3 (1.3.13)
stackprof (0.2.10)
state_machines (0.4.0)
state_machines-activemodel (0.4.0)
@@ -962,7 +962,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
- rspec_profiling
+ rspec_profiling (~> 0.0.5)
rubocop (~> 0.46.0)
rubocop-rspec (~> 1.9.1)
ruby-fogbugz (~> 0.2.1)
@@ -1011,4 +1011,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.14.2
+ 1.14.3
diff --git a/LICENSE b/LICENSE
index 1dc1bdb7411..ad4f2872db5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2016 GitLab B.V.
+Copyright (c) 2011-2017 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/PROCESS.md b/PROCESS.md
index 6eabaf05d24..f257c1d5358 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -59,20 +59,38 @@ star, smile, etc.). Some good tips about code reviews can be found in our
## Feature Freeze
-On the 7th of each month, the stable branches for the upcoming release will
-be frozen for major changes. Merge requests may still be merged into master
-during this period. By freezing the stable branches prior to a release there's
-no need to worry about last minute merge requests potentially breaking a lot of
-things.
+On the 7th of each month, RC1 of the upcoming release is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
+Merge requests may still be merged into master during this period,
+but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
+By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
-What is considered to be a major change is determined on a case by case basis as
-this definition depends very much on the context of changes. For example, a 5
-line change might have a big impact on the entire application. Ultimately the
-decision will be made by the maintainers and the release managers.
+Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
+and security issues will be cherry-picked into the stable branch.
+Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
+These fixes will be released in the next RC (before the 22nd) or patch release (after the 22nd).
+
+If you think a merge request should go into the upcoming release even though it does not meet these requirements,
+you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
+
+1. a Release Manager
+2. an Engineering Lead
+3. an Engineering Director, the VP of Engineering, or the CTO
+
+You can find who is who on the [team page](https://about.gitlab.com/team/).
+
+Whether an exception is made is determined by weighing the benefit and urgency of the change
+(how important it is to the company that this is released _right now_ instead of in a month)
+against the potential negative impact
+(things breaking without enough time to comfortably find and fix them before the release on the 22nd).
+When in doubt, we err on the side of _not_ cherry-picking.
+
+For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement
+(e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested.
During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label
-~"Pick into Stable" set. Merge requests without a milestone and this label will
+~"Pick into Stable" set, so that release managers can find and pick them.
+Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index ea3f13bd00f..4ecbf195b64 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -10,7 +10,6 @@ function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
-require('jquery-ui/ui/datepicker');
require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable');
@@ -35,8 +34,10 @@ require('bootstrap/js/transition');
require('bootstrap/js/tooltip');
require('bootstrap/js/popover');
require('select2/select2.js');
+window.Pikaday = require('pikaday');
window._ = require('underscore');
window.Dropzone = require('dropzone');
+window.Sortable = require('vendor/Sortable');
require('mousetrap');
require('mousetrap/plugins/pause/mousetrap-pause');
require('./shortcuts');
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index c345fb6ce14..8f30900198e 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -6,7 +6,6 @@ function requireAll(context) { return context.keys().map(context); }
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-window.Sortable = require('vendor/Sortable');
requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6
index 7e192e90fe6..ac2966cef5d 100644
--- a/app/assets/javascripts/boards/filters/due_date_filters.js.es6
+++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6
@@ -1,6 +1,7 @@
/* global Vue */
+/* global dateFormat */
Vue.filter('due-date', (value) => {
const date = new Date(value);
- return $.datepicker.formatDate('M d, yy', date);
+ return dateFormat(date, 'mmm d, yyyy');
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
index 11a3449d99a..f1b41911b73 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
@@ -26,7 +26,7 @@ class PipelinesStore {
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
- this.timeLoopInterval = setInterval(function timeloopInterval() {
+ this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6
index 2bfe57b4100..4bd537a6f28 100644
--- a/app/assets/javascripts/copy_as_gfm.js.es6
+++ b/app/assets/javascripts/copy_as_gfm.js.es6
@@ -91,6 +91,9 @@ require('./lib/utils/common_utils');
},
},
SanitizationFilter: {
+ 'a[name]:not([href]):empty'(el, text) {
+ return el.outerHTML;
+ },
'dl'(el, text) {
let lines = text.trim().split('\n');
// Add two spaces to the front of subsequent list items lines,
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6
index c39e30fb7e0..ccccd0a36ff 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js.es6
@@ -76,7 +76,7 @@ require('./lib/utils/url_utility');
const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
- const clickTarget = $('.file-title, .click-to-expand', diffFile);
+ const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
this.highlighSelectedLine();
if (cb) cb();
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
index 2514459e65e..d948dff58ec 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
@@ -1,6 +1,6 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
-/* global Vue */
/* global CommentsStore */
+const Vue = require('vue');
(() => {
const CommentAndResolveBtn = Vue.extend({
@@ -9,13 +9,11 @@
},
data() {
return {
- textareaIsEmpty: true
+ textareaIsEmpty: true,
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return CommentsStore.state[this.discussionId];
- },
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
@@ -42,6 +40,9 @@
}
}
},
+ created() {
+ this.discussion = CommentsStore.state[this.discussionId];
+ },
mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === '';
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
index c3898873eaa..57cb0d0ae6e 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
-/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
+const Vue = require('vue');
(() => {
const JumpToDiscussion = Vue.extend({
@@ -12,12 +12,10 @@
data: function () {
return {
discussions: CommentsStore.state,
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return this.discussions[this.discussionId];
- },
allResolved: function () {
return this.unresolvedDiscussionCount === 0;
},
@@ -186,7 +184,10 @@
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
});
}
- }
+ },
+ created() {
+ this.discussion = this.discussions[this.discussionId];
+ },
});
Vue.component('jump-to-discussion', JumpToDiscussion);
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
index 5852b8bbdb7..d1873d6c7a2 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
@@ -1,8 +1,8 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
-/* global Vue */
/* global CommentsStore */
/* global ResolveService */
/* global Flash */
+const Vue = require('vue');
(() => {
const ResolveBtn = Vue.extend({
@@ -10,14 +10,14 @@
noteId: Number,
discussionId: String,
resolved: Boolean,
- projectPath: String,
canResolve: Boolean,
resolvedBy: String
},
data: function () {
return {
discussions: CommentsStore.state,
- loading: false
+ loading: false,
+ note: {},
};
},
watch: {
@@ -30,13 +30,6 @@
discussion: function () {
return this.discussions[this.discussionId];
},
- note: function () {
- if (this.discussion) {
- return this.discussion.getNote(this.noteId);
- } else {
- return undefined;
- }
- },
buttonText: function () {
if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`;
@@ -73,10 +66,10 @@
if (this.isResolved) {
promise = ResolveService
- .unresolve(this.projectPath, this.noteId);
+ .unresolve(this.noteId);
} else {
promise = ResolveService
- .resolve(this.projectPath, this.noteId);
+ .resolve(this.noteId);
}
promise.then((response) => {
@@ -106,6 +99,8 @@
},
created: function () {
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
+
+ this.note = this.discussion.getNote(this.noteId);
}
});
diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
index 72cdae812bc..de9367f2136 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */
-/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
+const Vue = require('vue');
((w) => {
w.ResolveCount = Vue.extend({
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
index ee5f62b2d9e..7c5fcd04d2d 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
@@ -1,25 +1,22 @@
/* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */
-/* global Vue */
/* global CommentsStore */
/* global ResolveService */
+const Vue = require('vue');
+
(() => {
const ResolveDiscussionBtn = Vue.extend({
props: {
discussionId: String,
mergeRequestId: Number,
- projectPath: String,
canResolve: Boolean,
},
data: function() {
return {
- discussions: CommentsStore.state
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return this.discussions[this.discussionId];
- },
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
@@ -51,11 +48,13 @@
},
methods: {
resolve: function () {
- ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
+ ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
}
},
created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
+
+ this.discussion = CommentsStore.state[this.discussionId];
}
});
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
index f0edfb8aaf1..190461451d5 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
@@ -3,6 +3,7 @@
/* global ResolveCount */
function requireAll(context) { return context.keys().map(context); }
+const Vue = require('vue');
requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
@@ -10,11 +11,14 @@ requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
$(() => {
+ const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
+ window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
+
gl.diffNotesCompileComponents = () => {
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6
index a52c476352d..090c454e9e4 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js.es6
+++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6
@@ -1,45 +1,37 @@
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
-/* global Vue */
/* global Flash */
/* global CommentsStore */
-((w) => {
- class ResolveServiceClass {
- constructor() {
- this.noteResource = Vue.resource('notes{/noteId}/resolve');
- this.discussionResource = Vue.resource('merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve');
- }
+const Vue = window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
+require('../../vue_shared/vue_resource_interceptor');
- setCSRF() {
- Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
- }
+(() => {
+ window.gl = window.gl || {};
- prepareRequest(root) {
- this.setCSRF();
- Vue.http.options.root = root;
+ class ResolveServiceClass {
+ constructor(root) {
+ this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
+ this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
}
- resolve(projectPath, noteId) {
- this.prepareRequest(projectPath);
-
+ resolve(noteId) {
return this.noteResource.save({ noteId }, {});
}
- unresolve(projectPath, noteId) {
- this.prepareRequest(projectPath);
-
+ unresolve(noteId) {
return this.noteResource.delete({ noteId }, {});
}
- toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
+ toggleResolveForDiscussion(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
const isResolved = discussion.isResolved();
let promise;
if (isResolved) {
- promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
+ promise = this.unResolveAll(mergeRequestId, discussionId);
} else {
- promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
+ promise = this.resolveAll(mergeRequestId, discussionId);
}
promise.then((response) => {
@@ -62,11 +54,9 @@
});
}
- resolveAll(projectPath, mergeRequestId, discussionId) {
+ resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(projectPath);
-
discussion.loading = true;
return this.discussionResource.save({
@@ -75,11 +65,9 @@
}, {});
}
- unResolveAll(projectPath, mergeRequestId, discussionId) {
+ unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(projectPath);
-
discussion.loading = true;
return this.discussionResource.delete({
@@ -89,5 +77,5 @@
}
}
- w.ResolveService = new ResolveServiceClass();
-})(window);
+ gl.DiffNotesResolveServiceClass = ResolveServiceClass;
+})();
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index f8efca76b13..7eec2d39a9c 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -97,6 +97,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
+ case 'projects:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'));
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
index d81d4cf8425..ab5ce23d261 100644
--- a/app/assets/javascripts/due_date_select.js.es6
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -1,4 +1,6 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
+/* global dateFormat */
+/* global Pikaday */
(function(global) {
class DueDateSelect {
@@ -25,11 +27,14 @@
this.initGlDropdown();
this.initRemoveDueDate();
this.initDatePicker();
- this.initStopPropagation();
}
initGlDropdown() {
this.$dropdown.glDropdown({
+ opened: () => {
+ const calendar = this.$datePicker.data('pikaday');
+ calendar.show();
+ },
hidden: () => {
this.$selectbox.hide();
this.$value.css('display', '');
@@ -38,25 +43,37 @@
}
initDatePicker() {
- this.$datePicker.datepicker({
- dateFormat: 'yy-mm-dd',
- defaultDate: $("input[name='" + this.fieldName + "']").val(),
- altField: "input[name='" + this.fieldName + "']",
- onSelect: () => {
+ const $dueDateInput = $(`input[name='${this.fieldName}']`);
+
+ const calendar = new Pikaday({
+ field: $dueDateInput.get(0),
+ theme: 'gitlab-theme',
+ format: 'YYYY-MM-DD',
+ onSelect: (dateText) => {
+ const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
+
+ $dueDateInput.val(formattedDate);
+
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val();
+ gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
- return this.saveDueDate(true);
+ this.saveDueDate(true);
}
}
});
+
+ this.$datePicker.append(calendar.el);
+ this.$datePicker.data('pikaday', calendar);
}
initRemoveDueDate() {
this.$block.on('click', '.js-remove-due-date', (e) => {
+ const calendar = this.$datePicker.data('pikaday');
e.preventDefault();
+ calendar.setDate(null);
+
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
@@ -67,12 +84,6 @@
});
}
- initStopPropagation() {
- $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
- return e.stopImmediatePropagation();
- });
- }
-
saveDueDate(isDropdown) {
this.parseSelectedDate();
this.prepSelectedDate();
@@ -86,7 +97,7 @@
// Construct Date object manually to avoid buggy dateString support within Date constructor
const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10));
const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
- this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
+ this.displayedDate = dateFormat(dateObj, 'mmm d, yyyy');
} else {
this.displayedDate = 'No due date';
}
@@ -153,14 +164,24 @@
}
initMilestoneDatePicker() {
- $('.datepicker').datepicker({
- dateFormat: 'yy-mm-dd'
+ $('.datepicker').each(function() {
+ const $datePicker = $(this);
+ const calendar = new Pikaday({
+ field: $datePicker.get(0),
+ theme: 'gitlab-theme',
+ format: 'YYYY-MM-DD',
+ onSelect(dateText) {
+ $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
+ });
+
+ $datePicker.data('pikaday', calendar);
});
$('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
e.preventDefault();
- const datepicker = $(e.target).siblings('.datepicker');
- $.datepicker._clearDate(datepicker);
+ const calendar = $(e.target).siblings('.datepicker').data('pikaday');
+ calendar.setDate(null);
});
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index 859d6515531..e8c2df03a46 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -4,7 +4,7 @@
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
- this.hookId = input.getAttribute('data-id');
+ this.hookId = input && input.getAttribute('data-id');
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
index 547989a6ff5..8ce4cf4fc36 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
@@ -2,7 +2,8 @@
(() => {
class FilteredSearchDropdownManager {
- constructor() {
+ constructor(baseEndpoint = '') {
+ this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchInput = document.querySelector('.filtered-search');
@@ -38,13 +39,13 @@
milestone: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: ['milestones.json', '%'],
+ extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: ['labels.json', '~'],
+ extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'),
},
hint: {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
index 4e02ab7c8c1..ffc7d29e4c5 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -6,7 +6,7 @@
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager();
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '');
this.bindEvents();
this.loadSearchParamsFromURL();
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index d9101b55c7f..77fa662892a 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -437,7 +437,7 @@
}
};
- GitLabDropdown.prototype.opened = function() {
+ GitLabDropdown.prototype.opened = function(e) {
var contentHtml;
this.resetRows();
this.addArrowKeyEvent();
@@ -457,6 +457,10 @@
this.positionMenuAbove();
}
+ if (this.options.opened) {
+ this.options.opened.call(this, e);
+ }
+
return this.dropdown.trigger('shown.gl.dropdown');
};
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 293b856dc4d..2ec545db665 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -3,6 +3,8 @@
/* global UsersSelect */
/* global ZenMode */
/* global Autosave */
+/* global dateFormat */
+/* global Pikaday */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -13,7 +15,7 @@
IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
function IssuableForm(form) {
- var $issuableDueDate;
+ var $issuableDueDate, calendar;
this.form = form;
this.toggleWip = bind(this.toggleWip, this);
this.renderWipExplanation = bind(this.renderWipExplanation, this);
@@ -35,12 +37,14 @@
this.initMoveDropdown();
$issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) {
- $('.datepicker').datepicker({
- dateFormat: 'yy-mm-dd',
- onSelect: function(dateText, inst) {
- return $issuableDueDate.val(dateText);
+ calendar = new Pikaday({
+ field: $issuableDueDate.get(0),
+ theme: 'gitlab-theme',
+ format: 'YYYY-MM-DD',
+ onSelect: function(dateText) {
+ $issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
- }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val()));
+ });
}
}
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index 2a50b72c8aa..38b2eb9ff14 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Flash */
+/* global Sortable */
((global) => {
class LabelManager {
@@ -9,11 +10,12 @@
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
- this.prioritizedLabels.sortable({
- items: 'li',
- placeholder: 'list-placeholder',
- axis: 'y',
- update: this.onPrioritySortUpdate.bind(this)
+ this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
+ filter: '.empty-message',
+ forceFallback: true,
+ fallbackClass: 'is-dragging',
+ dataIdAttr: 'data-id',
+ onUpdate: this.onPrioritySortUpdate.bind(this),
});
this.bindEvents();
}
@@ -51,13 +53,13 @@
$target = this.otherLabels;
$from = this.prioritizedLabels;
}
- if ($from.find('li').length === 1) {
+ $label.detach().appendTo($target);
+ if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
- if (!$target.find('li').length) {
+ if ($target.find('> li:not(.empty-message)').length) {
$target.find('.empty-message').addClass('hidden');
}
- $label.detach().appendTo($target);
// Return if we are not persisting state
if (!persistState) {
return;
@@ -101,8 +103,12 @@
getSortedLabelsIds() {
const sortedIds = [];
- this.prioritizedLabels.find('li').each(function() {
- sortedIds.push($(this).data('id'));
+ this.prioritizedLabels.find('> li').each(function() {
+ const id = $(this).data('id');
+
+ if (id) {
+ sortedIds.push(id);
+ }
});
return sortedIds;
}
diff --git a/app/assets/javascripts/member_expiration_date.js.es6 b/app/assets/javascripts/member_expiration_date.js.es6
index bf6c0ec2798..f57d4a20498 100644
--- a/app/assets/javascripts/member_expiration_date.js.es6
+++ b/app/assets/javascripts/member_expiration_date.js.es6
@@ -1,3 +1,5 @@
+/* global Pikaday */
+/* global dateFormat */
(() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
@@ -11,21 +13,34 @@
}
const inputs = $(selector);
- inputs.datepicker({
- dateFormat: 'yy-mm-dd',
- minDate: 1,
- onSelect: function onSelect() {
- $(this).trigger('change');
- toggleClearInput.call(this);
- },
+ inputs.each((i, el) => {
+ const $input = $(el);
+
+ const calendar = new Pikaday({
+ field: $input.get(0),
+ theme: 'gitlab-theme',
+ format: 'YYYY-MM-DD',
+ minDate: new Date(),
+ onSelect(dateText) {
+ $input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+
+ $input.trigger('change');
+
+ toggleClearInput.call($input);
+ },
+ });
+
+ $input.data('pikaday', calendar);
});
inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault();
const input = $(this).closest('.clearable-input').find(selector);
- input.datepicker('setDate', null)
- .trigger('change');
+ const calendar = input.data('pikaday');
+
+ calendar.setDate(null);
+ input.trigger('change');
toggleClearInput.call(input);
});
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index e5d2d706fc7..69aed77c83d 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -154,7 +154,7 @@ require('./smart_interval');
return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) {
var message, status, title;
- if (data.status === '') {
+ if (!data.status) {
return;
}
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 7ce1259e015..051cb9fe5c5 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Flash */
+/* global Sortable */
(function() {
this.Milestone = (function() {
@@ -8,11 +9,9 @@
type: "PUT",
url: issue_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data, li);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data, li);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -27,11 +26,9 @@
type: "PUT",
url: sort_issues_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data);
+ },
error: function() {
return new Flash("Issues update failed", 'alert');
},
@@ -46,11 +43,9 @@
type: "PUT",
url: sort_mr_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -63,11 +58,9 @@
type: "PUT",
url: merge_request_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data, li);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data, li);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -81,65 +74,30 @@
img_tag = $('<img/>');
img_tag.attr('src', data.assignee.avatar_url);
img_tag.addClass('avatar s16');
- $(element).find('.assignee-icon').html(img_tag);
+ $(element).find('.assignee-icon img').replaceWith(img_tag);
} else {
- $(element).find('.assignee-icon').html('');
+ $(element).find('.assignee-icon').empty();
}
return $(element).effect('highlight');
};
function Milestone() {
var oldMouseStart;
- oldMouseStart = $.ui.sortable.prototype._mouseStart;
- $.ui.sortable.prototype._mouseStart = function(event, overrideHandle, noActivation) {
- this._trigger("beforeStart", event, this._uiHash());
- return oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
- };
this.bindIssuesSorting();
this.bindMergeRequestSorting();
this.bindTabsSwitching();
}
Milestone.prototype.bindIssuesSorting = function() {
- return $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable({
- connectWith: ".issues-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: function(event, ui) {
- return $(".issues-sortable-list").css("min-height", ui.item.outerHeight());
- },
- stop: function(event, ui) {
- return $(".issues-sortable-list").css("min-height", "0px");
- },
- update: function(event, ui) {
- var data;
- // Prevents sorting from container which element has been removed.
- if ($(this).find(ui.item).length > 0) {
- data = $(this).sortable("serialize");
- return Milestone.sortIssues(data);
- }
- },
- receive: function(event, ui) {
- var data, issue_id, issue_url, new_state;
- new_state = $(this).data('state');
- issue_id = ui.item.data('iid');
- issue_url = ui.item.data('url');
- data = (function() {
- switch (new_state) {
- case 'ongoing':
- return "issue[assignee_id]=" + gon.current_user_id;
- case 'unassigned':
- return "issue[assignee_id]=";
- case 'closed':
- return "issue[state_event]=close";
- }
- })();
- if ($(ui.sender).data('state') === "closed") {
- data += "&issue[state_event]=reopen";
- }
- return Milestone.updateIssue(ui.item, issue_url, data);
- }
- }).disableSelection();
+ $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
+ this.createSortable(el, {
+ group: 'issue-list',
+ listEls: $('.issues-sortable-list'),
+ fieldName: 'issue',
+ sortCallback: Milestone.sortIssues,
+ updateCallback: Milestone.updateIssue,
+ });
+ }.bind(this));
};
Milestone.prototype.bindTabsSwitching = function() {
@@ -154,42 +112,62 @@
};
Milestone.prototype.bindMergeRequestSorting = function() {
- return $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable({
- connectWith: ".merge_requests-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: function(event, ui) {
- return $(".merge_requests-sortable-list").css("min-height", ui.item.outerHeight());
+ $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
+ this.createSortable(el, {
+ group: 'merge-request-list',
+ listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
+ fieldName: 'merge_request',
+ sortCallback: Milestone.sortMergeRequests,
+ updateCallback: Milestone.updateMergeRequest,
+ });
+ }.bind(this));
+ };
+
+ Milestone.prototype.createSortable = function(el, opts) {
+ return Sortable.create(el, {
+ group: opts.group,
+ filter: '.is-disabled',
+ forceFallback: true,
+ onStart: function(e) {
+ opts.listEls.css('min-height', e.item.offsetHeight);
},
- stop: function(event, ui) {
- return $(".merge_requests-sortable-list").css("min-height", "0px");
+ onEnd: function () {
+ opts.listEls.css("min-height", "0px");
},
- update: function(event, ui) {
- var data;
- data = $(this).sortable("serialize");
- return Milestone.sortMergeRequests(data);
+ onUpdate: function(e) {
+ var ids = this.toArray(),
+ data;
+
+ if (ids.length) {
+ data = ids.map(function(id) {
+ return 'sortable_' + opts.fieldName + '[]=' + id;
+ }).join('&');
+
+ opts.sortCallback(data);
+ }
},
- receive: function(event, ui) {
- var data, merge_request_id, merge_request_url, new_state;
- new_state = $(this).data('state');
- merge_request_id = ui.item.data('iid');
- merge_request_url = ui.item.data('url');
+ onAdd: function (e) {
+ var data, issuableId, issuableUrl, newState;
+ newState = e.to.dataset.state;
+ issuableUrl = e.item.dataset.url;
data = (function() {
- switch (new_state) {
+ switch (newState) {
case 'ongoing':
- return "merge_request[assignee_id]=" + gon.current_user_id;
+ return opts.fieldName + '[assignee_id]=' + gon.current_user_id;
case 'unassigned':
- return "merge_request[assignee_id]=";
+ return opts.fieldName + '[assignee_id]=';
case 'closed':
- return "merge_request[state_event]=close";
+ return opts.fieldName + '[state_event]=close';
}
})();
- if ($(ui.sender).data('state') === "closed") {
- data += "&merge_request[state_event]=reopen";
+ if (e.from.dataset.state === 'closed') {
+ data += '&' + opts.fieldName + '[state_event]=reopen';
}
- return Milestone.updateMergeRequest(ui.item, merge_request_url, data);
+
+ opts.updateCallback(e.item, issuableUrl, data);
+ this.options.onUpdate.call(this, e);
}
- }).disableSelection();
+ });
};
return Milestone;
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
index 4becbc32681..919fcd0a07b 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
@@ -28,7 +28,7 @@
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
- $(this.container).on('shown.bs.dropdown', this.getBuildsList);
+ $(document).on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d108da29af7..3579843baed 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -455,7 +455,7 @@ require('vendor/task_list');
var mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) {
- ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
+ ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
}
}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 5b20c63384c..3ee0c73a8d2 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -33,13 +33,13 @@
this.$toggleIcon.addClass('fa-caret-down');
}
- $('.file-title, .click-to-expand', this.file).on('click', (function (e) {
+ $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target));
}).bind(this));
}
SingleFileDiff.prototype.toggleDiff = function($target, cb) {
- if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+ if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
this.content.hide();
diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index f05780167bf..7dba5840c8a 100644
--- a/app/assets/javascripts/boards/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -50,14 +50,15 @@
return (
children[target.index] ||
children[target.index === 'first' ? 0 : -1] ||
- children[target.index === 'last' ? children.length - 1 : -1]
+ children[target.index === 'last' ? children.length - 1 : -1] ||
+ el
);
}
function getRect(el) {
var rect = el.getBoundingClientRect();
var width = rect.right - rect.left;
- var height = rect.bottom - rect.top;
+ var height = rect.bottom - rect.top + 10;
return {
x: rect.left,
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js.es6 b/app/assets/javascripts/vue_realtime_listener/index.js.es6
index 95564152cce..30f6680a673 100644
--- a/app/assets/javascripts/vue_realtime_listener/index.js.es6
+++ b/app/assets/javascripts/vue_realtime_listener/index.js.es6
@@ -14,5 +14,16 @@
window.addEventListener('focus', startIntervals);
window.addEventListener('blur', removeIntervals);
document.addEventListener('beforeunload', removeAll);
+
+ // add removeAll methods to stack
+ const stack = gl.VueRealtimeListener.reset;
+ gl.VueRealtimeListener.reset = () => {
+ gl.VueRealtimeListener.reset = stack;
+ removeAll();
+ stack();
+ };
};
+
+ // remove all event listeners and intervals
+ gl.VueRealtimeListener.reset = () => undefined; // noop
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
index c819f0dd7cd..61c1b72d9d2 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
@@ -111,7 +111,7 @@ require('./commit');
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
- * Matched `url` prop sent in the API to `path` prop needed
+ * Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
@@ -119,8 +119,8 @@ require('./commit');
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
- if (prop === 'url') {
- accumulator.path = this.pipeline.ref[prop];
+ if (prop === 'path') {
+ accumulator.ref_url = this.pipeline.ref[prop];
} else {
accumulator[prop] = this.pipeline.ref[prop];
}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 8b93665d085..1dcd1f8a6fc 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery-ui/datepicker
*= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
@@ -19,6 +18,8 @@
* directory.
*/
+@import "../../../node_modules/pikaday/scss/pikaday";
+
/*
* GitLab UI framework
*/
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 1d2d1bfc0d7..fb8ea18d122 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -9,6 +9,8 @@
}
.user-calendar-activities {
+ direction: ltr;
+
.str-truncated {
max-width: 70%;
}
@@ -43,3 +45,56 @@
float: right;
font-size: 12px;
}
+
+.pika-single.gitlab-theme {
+ .pika-label {
+ color: $gl-text-color-secondary;
+ font-size: 14px;
+ font-weight: normal;
+ }
+
+ th {
+ padding: 2px 0;
+ color: $note-disabled-comment-color;
+ font-weight: normal;
+ text-transform: lowercase;
+ border-top: 1px solid $calendar-border-color;
+ }
+
+ abbr {
+ cursor: default;
+ }
+
+ td {
+ border: 1px solid $calendar-border-color;
+
+ &:first-child {
+ border-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+ }
+
+ .pika-day {
+ border-radius: 0;
+ background-color: $white-light;
+ text-align: center;
+ }
+
+ .is-today {
+ .pika-day {
+ color: inherit;
+ font-weight: normal;
+ }
+ }
+
+ .is-selected .pika-day,
+ .pika-day:hover,
+ .is-today .pika-day:hover {
+ background: $gl-primary;
+ color: $white-light;
+ box-shadow: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ca5861bf3e6..ff31e7f7b3d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -125,7 +125,6 @@
top: 100%;
left: 0;
z-index: 9;
- max-width: 280px;
min-width: 240px;
margin-top: 2px;
margin-bottom: 0;
@@ -137,6 +136,10 @@
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
+ .filtered-search-input-container & {
+ max-width: 280px;
+ }
+
&.is-loading {
.dropdown-content {
display: none;
@@ -502,119 +505,16 @@
max-height: 230px;
}
- .ui-widget {
- table {
- margin: 0;
- }
-
- &.ui-datepicker-inline {
- padding: 0 10px;
- border: 0;
- width: 100%;
- }
-
- .ui-datepicker-header {
- padding: 0 8px 10px;
- border: 0;
-
- .ui-icon {
- background: none;
- font-size: 20px;
- text-indent: 0;
-
- &::before {
- display: block;
- position: relative;
- top: -2px;
- color: $dropdown-title-btn-color;
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- }
- }
- }
-
- .ui-datepicker-calendar {
- .ui-state-hover,
- .ui-state-active {
- color: $white-light;
- border: 0;
- }
- }
-
- .ui-datepicker-prev,
- .ui-datepicker-next {
- top: 0;
- height: 15px;
- cursor: pointer;
-
- &:hover {
- background-color: transparent;
- border: 0;
-
- .ui-icon::before {
- color: $md-link-color;
- }
- }
- }
-
- .ui-datepicker-prev {
- left: 0;
-
- .ui-icon::before {
- content: '\f104';
- text-align: left;
- }
- }
-
- .ui-datepicker-next {
- right: 0;
-
- .ui-icon::before {
- content: '\f105';
- text-align: right;
- }
- }
-
- td {
- padding: 0;
- border: 1px solid $calendar-border-color;
-
- &:first-child {
- border-left: 0;
- }
-
- &:last-child {
- border-right: 0;
- }
-
- a {
- line-height: 17px;
- border: 0;
- border-radius: 0;
- }
- }
-
- .ui-datepicker-title {
- color: $gl-text-color;
- font-size: 14px;
- line-height: 1;
- font-weight: normal;
- }
- }
-
- th {
- padding: 2px 0;
- color: $note-disabled-comment-color;
- font-weight: normal;
- text-transform: lowercase;
- border-top: 1px solid $calendar-border-color;
+ .pika-single {
+ position: relative!important;
+ top: 0!important;
+ border: 0;
+ box-shadow: none;
}
- .ui-datepicker-unselectable {
- background-color: $gray-light;
+ .pika-lendar {
+ margin-top: -5px;
+ margin-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index c51912b4ac4..30f242a35db 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -231,3 +231,46 @@ span.idiff {
}
}
}
+
+.file-title-flex-parent {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: $gray-light;
+ border-bottom: 1px solid $border-color;
+ padding: 5px $gl-padding;
+ margin: 0;
+ border-radius: 3px 3px 0 0;
+
+ .file-header-content {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-right: 30px;
+ position: relative;
+ }
+
+ .btn-clipboard {
+ position: absolute;
+ right: 0;
+ }
+
+ a {
+ color: $gl-text-color;
+ }
+
+ small {
+ margin: 0 10px 0 0;
+ }
+
+ .file-actions {
+ white-space: nowrap;
+
+ .btn {
+ padding: 0 10px;
+ font-size: 13px;
+ line-height: 28px;
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 18f2f316f02..d335fedefe2 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -2,42 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
- &.ui-datepicker,
- &.ui-datepicker-inline {
- border: 1px solid $jq-ui-border;
- padding: 10px;
- width: 270px;
-
- .ui-datepicker-header {
- background: $white-light;
- border-color: $jq-ui-border;
-
- .ui-datepicker-prev,
- .ui-datepicker-next {
- top: 4px;
- }
-
- .ui-datepicker-prev {
- left: 2px;
- }
-
- .ui-datepicker-next {
- right: 2px;
- }
-
- .ui-state-hover {
- background: transparent;
- border: 0;
- cursor: pointer;
- }
- }
-
- .ui-datepicker-calendar td a {
- padding: 5px;
- text-align: center;
- }
- }
-
&.ui-autocomplete {
border-color: $jq-ui-border;
padding: 0;
@@ -59,25 +23,4 @@
border: 0;
background: transparent;
}
-
- .ui-datepicker-calendar {
- .ui-state-active,
- .ui-state-hover,
- .ui-state-focus {
- border: 1px solid $gl-primary;
- background: $gl-primary;
- color: $white-light;
- }
- }
-}
-
-.ui-sortable-handle {
- cursor: move;
- cursor: -webkit-grab;
- cursor: -moz-grab;
-
- &:active {
- cursor: -webkit-grabbing;
- cursor: -moz-grabbing;
- }
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 96ba7c40634..92d7772da57 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -34,9 +34,14 @@
}
}
- .file-title {
+ .file-title,
+ .file-title-flex-parent {
cursor: pointer;
+ a:hover {
+ text-decoration: none;
+ }
+
&:hover {
background-color: $gray-normal;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9174976c4c6..1a53730bed5 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -461,8 +461,19 @@
.issuable-list {
li {
+
+ .issue-box {
+ display: -webkit-flex;
+ display: flex;
+ }
+
+ .issue-info-container {
+ -webkit-flex: 1;
+ flex: 1;
+ padding-right: $gl-padding;
+ }
+
.issue-check {
- float: left;
padding-right: $gl-padding;
margin-bottom: 10px;
min-width: 15px;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 1e605337f09..80b0c9493d8 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -1,6 +1,6 @@
.issues-list {
.issue {
- padding: 10px $gl-padding;
+ padding: 10px 0 10px $gl-padding;
position: relative;
.title {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 762b95a657c..e1ef0b029a5 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -116,6 +116,22 @@
}
.manage-labels-list {
+ > li:not(.empty-message) {
+ background-color: $white-light;
+ cursor: move;
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+
+ &:active {
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ }
+
+ &.sortable-ghost {
+ opacity: 0.3;
+ }
+ }
+
.btn-action {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index b01d8d695d6..692142c5887 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -125,6 +125,12 @@
line-height: 16px;
}
+ @media (min-width: $screen-sm-min) {
+ .stage-cell {
+ padding: 0 4px;
+ }
+ }
+
@media (max-width: $screen-xs-max) {
order: 1;
margin-top: $gl-padding-top;
@@ -266,8 +272,15 @@
.mr-list {
.merge-request {
- padding: 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
+ display: -webkit-flex;
+ display: flex;
+
+ .issue-info-container {
+ -webkit-flex: 1;
+ flex: 1;
+ }
.merge-request-title {
margin-bottom: 2px;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 686b64cdd24..3da1150f89b 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -178,3 +178,9 @@
}
}
}
+
+.issuable-row {
+ background-color: $white-light;
+ cursor: -webkit-grab;
+ cursor: grab;
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 722b3006f7c..8031c4467a4 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -201,10 +201,6 @@
color: $note-disabled-comment-color;
}
-.datepicker.personal-access-tokens-expires-at .ui-state-disabled span {
- text-align: center;
-}
-
.created-personal-access-token-container {
#created-personal-access-token {
width: 90%;
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index c491e5c7550..8360ce08bdc 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,7 +1,7 @@
class Admin::DashboardController < Admin::ApplicationController
def index
- @projects = Project.limit(10)
+ @projects = Project.with_route.limit(10)
@users = User.limit(10)
- @groups = Group.limit(10)
+ @groups = Group.with_route.limit(10)
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index b7722a1d15d..cea3d088e94 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -2,7 +2,7 @@ class Admin::GroupsController < Admin::ApplicationController
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
- @groups = Group.with_statistics
+ @groups = Group.with_statistics.with_route
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
@@ -49,7 +49,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).async_execute
+ Groups::DestroyService.new(@group, current_user).async_execute
redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
index de6bc689bb7..0b7cf8167f0 100644
--- a/app/controllers/dashboard/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -1,5 +1,5 @@
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
- @group_members = current_user.group_members.includes(:source).page(params[:page])
+ @group_members = current_user.group_members.includes(source: :route).page(params[:page])
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 264b14713fb..7ed54479599 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -13,9 +13,11 @@ class GroupsController < Groups::ApplicationController
before_action :authorize_create_group!, only: [:new, :create]
# Load group projects
- before_action :group_projects, only: [:show, :projects, :activity, :issues, :merge_requests]
+ before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity]
+ before_action :user_actions, only: [:show, :subgroups]
+
layout :determine_layout
def index
@@ -37,13 +39,6 @@ class GroupsController < Groups::ApplicationController
end
def show
- if current_user
- @last_push = current_user.recent_push
- @notification_setting = current_user.notification_settings_for(group)
- end
-
- @nested_groups = group.children
-
setup_projects
respond_to do |format|
@@ -62,6 +57,11 @@ class GroupsController < Groups::ApplicationController
end
end
+ def subgroups
+ @nested_groups = group.children
+ @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
+ end
+
def activity
respond_to do |format|
format.html
@@ -91,7 +91,7 @@ class GroupsController < Groups::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).async_execute
+ Groups::DestroyService.new(@group, current_user).async_execute
redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
@@ -99,13 +99,16 @@ class GroupsController < Groups::ApplicationController
protected
def setup_projects
+ options = {}
+ options[:only_owned] = true if params[:shared] == '0'
+ options[:only_shared] = true if params[:shared] == '1'
+
+ @projects = GroupProjectsFinder.new(group, options).execute(current_user)
@projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
-
- @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
end
def authorize_create_group!
@@ -138,7 +141,8 @@ class GroupsController < Groups::ApplicationController
:public,
:request_access_enabled,
:share_with_group_lock,
- :visibility_level
+ :visibility_level,
+ :parent_id
]
end
@@ -147,4 +151,11 @@ class GroupsController < Groups::ApplicationController
@events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
+
+ def user_actions
+ if current_user
+ @last_push = current_user.recent_push
+ @notification_setting = current_user.notification_settings_for(group)
+ end
+ end
end
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index 440259b643c..8a5a645ed0e 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -48,6 +48,10 @@ class Projects::LfsApiController < Projects::GitHttpClientController
objects.each do |object|
if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object)
+
+ if Guest.can?(:download_code, project)
+ object[:authenticated] = true
+ end
else
object[:error] = {
code: 404,
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index c5d93ce25bc..b033f7b5ea9 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -51,7 +51,7 @@ class Projects::NotesController < Projects::ApplicationController
def destroy
if note.editable?
- Notes::DeleteService.new(project, current_user).execute(note)
+ Notes::DestroyService.new(project, current_user).execute(note)
end
respond_to do |format|
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 9a438d5512c..2f422d352ed 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -68,8 +68,12 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
def access_levels_options
{
- push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
- merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
+ push_access_levels: {
+ "Roles" => ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
+ },
+ merge_access_levels: {
+ "Roles" => ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
+ }
}
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 68bf01ba08d..b44f38d4a0c 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -24,7 +24,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
- DeleteUserService.new(current_user).execute(current_user)
+ Users::DestroyService.new(current_user).execute(current_user)
respond_to do |format|
format.html do
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 4e43f42e9e1..d932a17883f 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -2,7 +2,7 @@ class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
- find_union(segments, Group).order_id_desc
+ find_union(segments, Group).with_route.order_id_desc
end
private
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c7911736812..18ec45f300d 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -3,7 +3,7 @@ class ProjectsFinder < UnionFinder
segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
- find_union(segments, Project)
+ find_union(segments, Project).with_route
end
private
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 91b24b8bc29..b5f8c23a667 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -64,11 +64,11 @@ module MergeRequestsHelper
end
def mr_closes_issues
- @mr_closes_issues ||= @merge_request.closes_issues
+ @mr_closes_issues ||= @merge_request.closes_issues(current_user)
end
def mr_issues_mentioned_but_not_closing
- @mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing
+ @mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing(current_user)
end
def mr_change_branches_path(merge_request)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index c568cca9e5e..d7d51c99979 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -86,7 +86,9 @@ module TodosHelper
[
{ id: '', text: 'Any Action' },
{ id: Todo::ASSIGNED, text: 'Assigned' },
- { id: Todo::MENTIONED, text: 'Mentioned' }
+ { id: Todo::MENTIONED, text: 'Mentioned' },
+ { id: Todo::MARKED, text: 'Added' },
+ { id: Todo::BUILD_FAILED, text: 'Pipelines' }
]
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5213ea9d02b..8c1b076c2d7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -20,7 +20,7 @@ module Ci
end
serialize :options
- serialize :yaml_variables, Gitlab::Serialize::Ci::Variables
+ serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 2b93aa30c0f..9f6d215ceb3 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -1,5 +1,5 @@
# Store object full path in separate table for easy lookup and uniq validation
-# Object must have path db field and respond to full_path and full_path_changed? methods.
+# Object must have name and path db fields and respond to parent and parent_changed? methods.
module Routable
extend ActiveSupport::Concern
@@ -9,7 +9,13 @@ module Routable
validates_associated :route
validates :route, presence: true
- before_validation :update_route_path, if: :full_path_changed?
+ scope :with_route, -> { includes(:route) }
+
+ before_validation do
+ if full_path_changed? || full_name_changed?
+ prepare_route
+ end
+ end
end
class_methods do
@@ -77,10 +83,62 @@ module Routable
end
end
+ def full_name
+ if route && route.name.present?
+ @full_name ||= route.name
+ else
+ update_route if persisted?
+
+ build_full_name
+ end
+ end
+
+ def full_path
+ if route && route.path.present?
+ @full_path ||= route.path
+ else
+ update_route if persisted?
+
+ build_full_path
+ end
+ end
+
private
- def update_route_path
+ def full_name_changed?
+ name_changed? || parent_changed?
+ end
+
+ def full_path_changed?
+ path_changed? || parent_changed?
+ end
+
+ def build_full_name
+ if parent && name
+ parent.human_name + ' / ' + name
+ else
+ name
+ end
+ end
+
+ def build_full_path
+ if parent && path
+ parent.full_path + '/' + path
+ else
+ path
+ end
+ end
+
+ def update_route
+ prepare_route
+ route.save
+ end
+
+ def prepare_route
route || build_route(source: self)
- route.path = full_path
+ route.path = build_full_path
+ route.name = build_full_name
+ @full_path = nil
+ @full_name = nil
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 43085f69105..c0d4dd0197f 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -546,7 +546,7 @@ class MergeRequest < ActiveRecord::Base
# Calculating this information for a number of merge requests requires
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
- def cache_merge_request_closes_issues!(current_user = self.author)
+ def cache_merge_request_closes_issues!(current_user)
return if project.has_external_issue_tracker?
transaction do
@@ -558,10 +558,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- def closes_issue?(issue)
- closes_issues.include?(issue)
- end
-
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
@@ -575,13 +571,13 @@ class MergeRequest < ActiveRecord::Base
end
end
- def issues_mentioned_but_not_closing(current_user = self.author)
+ def issues_mentioned_but_not_closing(current_user)
return [] unless target_branch == project.default_branch
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(description)
- ext.issues - closes_issues
+ ext.issues - closes_issues(current_user)
end
def target_project_path
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index c5713fb7818..6de4d08fc28 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -7,6 +7,11 @@ class Namespace < ActiveRecord::Base
include Gitlab::CurrentSettings
include Routable
+ # Prevent users from creating unreasonably deep level of nesting.
+ # The number 20 was taken based on maximum nesting level of
+ # Android repo (15) + some extra backup.
+ NUMBER_OF_ANCESTORS_ALLOWED = 20
+
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy
@@ -29,6 +34,8 @@ class Namespace < ActiveRecord::Base
length: { maximum: 255 },
namespace: true
+ validate :nesting_level_allowed
+
delegate :name, to: :owner, allow_nil: true, prefix: true
after_update :move_dir, if: :path_changed?
@@ -170,31 +177,14 @@ class Namespace < ActiveRecord::Base
Gitlab.config.lfs.enabled
end
- def full_path
- if parent
- parent.full_path + '/' + path
- else
- path
- end
- end
-
def shared_runners_enabled?
projects.with_shared_runners.any?
end
- def full_name
- @full_name ||=
- if parent
- parent.full_name + ' / ' + name
- else
- name
- end
- end
-
# Scopes the model on ancestors of the record
def ancestors
if parent_id
- path = route.path
+ path = route ? route.path : full_path
paths = []
until path.blank?
@@ -217,6 +207,10 @@ class Namespace < ActiveRecord::Base
[owner_id]
end
+ def parent_changed?
+ parent_id_changed?
+ end
+
private
def repository_storage_paths
@@ -255,10 +249,6 @@ class Namespace < ActiveRecord::Base
find_each(&:refresh_members_authorized_projects)
end
- def full_path_changed?
- path_changed? || parent_id_changed?
- end
-
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
@@ -274,4 +264,10 @@ class Namespace < ActiveRecord::Base
path_was
end
end
+
+ def nesting_level_allowed
+ if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED
+ errors.add(:parent_id, "has too deep level of nesting")
+ end
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index b45f22d94d9..c17bcedf7b2 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -228,7 +228,12 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
- scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
+ scope :inside_path, ->(path) do
+ # We need routes alias rs for JOIN so it does not conflict with
+ # includes(:route) which we use in ProjectsFinder.
+ joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'").
+ where('rs.path LIKE ?', "#{path}/%")
+ end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@@ -810,26 +815,6 @@ class Project < ActiveRecord::Base
end
end
- def name_with_namespace
- @name_with_namespace ||= begin
- if namespace
- namespace.human_name + ' / ' + name
- else
- name
- end
- end
- end
- alias_method :human_name, :name_with_namespace
-
- def full_path
- if namespace && path
- namespace.full_path + '/' + path
- else
- path
- end
- end
- alias_method :path_with_namespace, :full_path
-
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
@@ -1328,6 +1313,18 @@ class Project < ActiveRecord::Base
map.public_path_for_source_path(path)
end
+ def parent
+ namespace
+ end
+
+ def parent_changed?
+ namespace_id_changed?
+ end
+
+ alias_method :name_with_namespace, :full_name
+ alias_method :human_name, :full_name
+ alias_method :path_with_namespace, :full_path
+
private
def cross_namespace_reference?(from)
@@ -1366,10 +1363,6 @@ class Project < ActiveRecord::Base
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end
- def full_path_changed?
- path_changed? || namespace_id_changed?
- end
-
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 80d002f9c32..eef403dba92 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -250,21 +250,11 @@ class JiraService < IssueTrackerService
end
end
- # Build remote link on JIRA properties
- # Icons here must be available on WEB so JIRA can read the URL
- # We are using a open word graphics icon which have LGPL license
def build_remote_link_props(url:, title:, resolved: false)
status = {
resolved: resolved
}
- if resolved
- status[:icon] = {
- title: 'Closed',
- url16x16: 'http://www.openwebgraphics.com/resources/data/1768/16x16_apply.png'
- }
- end
-
{
GlobalID: 'GitLab',
object: {
diff --git a/app/models/route.rb b/app/models/route.rb
index dd171fdb069..73574a6206b 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,16 +8,27 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- after_update :rename_descendants, if: :path_changed?
+ after_update :rename_descendants
def rename_descendants
- # We update each row separately because MySQL does not have regexp_replace.
- # rubocop:disable Rails/FindEach
- Route.where('path LIKE ?', "#{path_was}/%").each do |route|
- # Note that update column skips validation and callbacks.
- # We need this to avoid recursive call of rename_descendants method
- route.update_column(:path, route.path.sub(path_was, path))
+ if path_changed? || name_changed?
+ descendants = Route.where('path LIKE ?', "#{path_was}/%")
+
+ descendants.each do |route|
+ attributes = {}
+
+ if path_changed? && route.path.present?
+ attributes[:path] = route.path.sub(path_was, path)
+ end
+
+ if name_changed? && route.name.present?
+ attributes[:name] = route.name.sub(name_was, name)
+ end
+
+ # Note that update_columns skips validation and callbacks.
+ # We need this to avoid recursive call of rename_descendants method
+ route.update_columns(attributes) unless attributes.empty?
+ end
end
- # rubocop:enable Rails/FindEach
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index f64d0c17a45..33666b4f35b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -119,7 +119,7 @@ class User < ActiveRecord::Base
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
- before_validation :signup_domain_valid?, on: :create
+ before_validation :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index 91955542f25..fe16a3784c4 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -1,3 +1,50 @@
class EnvironmentSerializer < BaseSerializer
+ Item = Struct.new(:name, :size, :latest)
+
entity EnvironmentEntity
+
+ def within_folders
+ tap { @itemize = true }
+ end
+
+ def with_pagination(request, response)
+ tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
+ end
+
+ def itemized?
+ @itemize
+ end
+
+ def paginated?
+ @paginator.present?
+ end
+
+ def represent(resource, opts = {})
+ resource = @paginator.paginate(resource) if paginated?
+
+ if itemized?
+ itemize(resource).map do |item|
+ { name: item.name,
+ size: item.size,
+ latest: super(item.latest, opts) }
+ end
+ else
+ super(resource, opts)
+ end
+ end
+
+ private
+
+ def itemize(resource)
+ items = resource.group(:item_name).order('item_name ASC')
+ .pluck('COALESCE(environment_type, name) AS item_name',
+ 'COUNT(*) AS environments_count',
+ 'MAX(id) AS last_environment_id')
+
+ environments = resource.where(id: items.map(&:last)).index_by(&:id)
+
+ items.map do |name, size, id|
+ Item.new(name, size, environments[id])
+ end
+ end
end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index b2de6c5832e..2bc6cf3266e 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -1,41 +1,25 @@
class PipelineSerializer < BaseSerializer
class InvalidResourceError < StandardError; end
- include API::Helpers::Pagination
- Struct.new('Pagination', :request, :response)
entity PipelineEntity
- def represent(resource, opts = {})
- if paginated?
- raise InvalidResourceError unless resource.respond_to?(:page)
-
- super(paginate(resource.includes(project: :namespace)), opts)
- else
- super(resource, opts)
- end
- end
-
- def paginated?
- defined?(@pagination)
- end
-
def with_pagination(request, response)
- tap { @pagination = Struct::Pagination.new(request, response) }
+ tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
end
- private
-
- # Methods needed by `API::Helpers::Pagination`
- #
- def params
- @pagination.request.query_parameters
+ def paginated?
+ @paginator.present?
end
- def request
- @pagination.request
- end
+ def represent(resource, opts = {})
+ if resource.is_a?(ActiveRecord::Relation)
+ resource = resource.includes(project: :namespace)
+ end
- def header(header, value)
- @pagination.response.headers[header] = value
+ if paginated?
+ super(@paginator.paginate(resource), opts)
+ else
+ super(resource, opts)
+ end
end
end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
deleted file mode 100644
index eaff88d6463..00000000000
--- a/app/services/delete_user_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class DeleteUserService
- attr_accessor :current_user
-
- def initialize(current_user)
- @current_user = current_user
- end
-
- def execute(user, options = {})
- if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present?
- user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
- return user
- end
-
- user.solo_owned_groups.each do |group|
- DestroyGroupService.new(group, current_user).execute
- end
-
- user.personal_projects.each do |project|
- # Skip repository removal because we remove directory with namespace
- # that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
- end
-
- # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
- namespace = user.namespace
- user_data = user.destroy
- namespace.really_destroy!
-
- user_data
- end
-end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
deleted file mode 100644
index 2316c57bf1e..00000000000
--- a/app/services/destroy_group_service.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class DestroyGroupService
- attr_accessor :group, :current_user
-
- def initialize(group, user)
- @group, @current_user = group, user
- end
-
- def async_execute
- # Soft delete via paranoia gem
- group.destroy
- job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
- Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
- end
-
- def execute
- group.projects.each do |project|
- # Execute the destruction of the models immediately to ensure atomic cleanup.
- # Skip repository removal because we remove directory with namespace
- # that contain all these repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
- end
-
- group.children.each do |group|
- DestroyGroupService.new(group, current_user).async_execute
- end
-
- group.really_destroy!
- end
-end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
new file mode 100644
index 00000000000..7f2d28086f5
--- /dev/null
+++ b/app/services/groups/destroy_service.rb
@@ -0,0 +1,25 @@
+module Groups
+ class DestroyService < Groups::BaseService
+ def async_execute
+ # Soft delete via paranoia gem
+ group.destroy
+ job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
+ Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
+ end
+
+ def execute
+ group.projects.each do |project|
+ # Execute the destruction of the models immediately to ensure atomic cleanup.
+ # Skip repository removal because we remove directory with namespace
+ # that contain all these repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
+ group.children.each do |group|
+ DestroyService.new(group, current_user).async_execute
+ end
+
+ group.really_destroy!
+ end
+ end
+end
diff --git a/app/services/notes/delete_service.rb b/app/services/notes/destroy_service.rb
index a673e8e9dde..b819bd17039 100644
--- a/app/services/notes/delete_service.rb
+++ b/app/services/notes/destroy_service.rb
@@ -1,5 +1,5 @@
module Notes
- class DeleteService < BaseService
+ class DestroyService < BaseService
def execute(note)
note.destroy
end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
new file mode 100644
index 00000000000..2d11305be13
--- /dev/null
+++ b/app/services/users/destroy_service.rb
@@ -0,0 +1,33 @@
+module Users
+ class DestroyService
+ attr_accessor :current_user
+
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user, options = {})
+ if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present?
+ user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
+ return user
+ end
+
+ user.solo_owned_groups.each do |group|
+ Groups::DestroyService.new(group, current_user).execute
+ end
+
+ user.personal_projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
+ end
+
+ # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
+ namespace = user.namespace
+ user_data = user.destroy
+ namespace.really_destroy!
+
+ user_data
+ end
+ end
+end
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 0a954c20fcd..13d00dd1fcb 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -18,7 +18,7 @@
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
id: klass::file_name_noext }
.file-holder#README
- .file-title
+ .js-file-title.file-title
%i.fa.fa-file
= klass::file_name
.pull-right
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index b0bee1c6204..dfbc7772698 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -11,7 +11,7 @@
.form-group
.col-sm-12
.file-holder
- .file-title.clearfix
+ .js-file-title.file-title.clearfix
Content of .gitlab-ci.yml
#ci-editor.ci-editor= @content
= text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 3a95a652810..94408b92374 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -2,7 +2,7 @@
- blob = discussion.blob
.diff-file.file-holder
- .file-title
+ .js-file-title.file-title
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
.diff-content.code.js-syntax-highlight
diff --git a/app/views/discussions/_resolve_all.html.haml b/app/views/discussions/_resolve_all.html.haml
index f0b61e0f7de..e30ee1b0e05 100644
--- a/app/views/discussions/_resolve_all.html.haml
+++ b/app/views/discussions/_resolve_all.html.haml
@@ -1,6 +1,5 @@
- if discussion.for_merge_request?
- %resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'",
- ":discussion-id" => "'#{discussion.id}'",
+ %resolve-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
":merge-request-id" => discussion.noteable.iid,
":can-resolve" => discussion.can_resolve?(current_user),
"inline-template" => true }
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
new file mode 100644
index 00000000000..41f54f6bf42
--- /dev/null
+++ b/app/views/groups/_home_panel.html.haml
@@ -0,0 +1,17 @@
+.group-home-panel.text-center
+ %div{ class: container_class }
+ .avatar-container.s70.group-avatar
+ = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
+ %h1.group-title
+ @#{@group.path}
+ %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, fw: false)
+
+ - if @group.description.present?
+ .group-home-desc
+ = markdown_field(@group, :description)
+
+ - if current_user
+ .group-buttons
+ = render 'shared/members/access_request_buttons', source: @group
+ = render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/groups/_show_nav.html.haml b/app/views/groups/_show_nav.html.haml
new file mode 100644
index 00000000000..b2097e88741
--- /dev/null
+++ b/app/views/groups/_show_nav.html.haml
@@ -0,0 +1,7 @@
+%ul.nav-links
+ = nav_link(page: group_path(@group)) do
+ = link_to group_path(@group) do
+ Projects
+ = nav_link(page: subgroups_group_path(@group)) do
+ = link_to subgroups_group_path(@group) do
+ Subgroups
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index fb6f0da28f8..e66a8e0a3b3 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,8 @@
= render "header_title"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
= render 'shared/milestones/top', milestone: @milestone, group: @group
= render 'shared/milestones/summary', milestone: @milestone
= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index d256d14609e..b040f404ac4 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -4,38 +4,12 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-.group-home-panel.text-center
- %div{ class: container_class }
- .avatar-container.s70.group-avatar
- = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
- %h1.group-title
- @#{@group.path}
- %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
- = visibility_level_icon(@group.visibility_level, fw: false)
+= render 'groups/home_panel'
- - if @group.description.present?
- .group-home-desc
- = markdown_field(@group, :description)
-
- - if current_user
- .group-buttons
- = render 'shared/members/access_request_buttons', source: @group
- = render 'shared/notifications/button', notification_setting: @notification_setting
.groups-header{ class: container_class }
.top-area
- %ul.nav-links
- %li.active
- = link_to "#projects", 'data-toggle' => 'tab' do
- All Projects
- - if @shared_projects.present?
- %li
- = link_to "#shared", 'data-toggle' => 'tab' do
- Shared Projects
- - if @nested_groups.present?
- %li
- = link_to "#groups", 'data-toggle' => 'tab' do
- Subgroups
+ = render 'groups/show_nav'
.nav-controls
= form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
@@ -44,15 +18,4 @@
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
New Project
- .tab-content
- .tab-pane.active#projects
- = render "projects", projects: @projects
-
- - if @shared_projects.present?
- .tab-pane#shared
- = render "shared_projects", projects: @shared_projects
-
- - if @nested_groups.present?
- .tab-pane#groups
- %ul.content-list
- = render partial: 'shared/groups/group', collection: @nested_groups
+ = render "projects", projects: @projects
diff --git a/app/views/groups/subgroups.html.haml b/app/views/groups/subgroups.html.haml
new file mode 100644
index 00000000000..8610ae7e0ef
--- /dev/null
+++ b/app/views/groups/subgroups.html.haml
@@ -0,0 +1,20 @@
+- @no_container = true
+
+= render 'groups/home_panel'
+
+.groups-header{ class: container_class }
+ .top-area
+ = render 'groups/show_nav'
+ .nav-controls
+ = form_tag request.path, method: :get do |f|
+ = search_field_tag :filter_groups, params[:filter_groups], placeholder: 'Filter by name', class: 'form-control', spellcheck: false
+ - if can? current_user, :admin_group, @group
+ = link_to new_group_path(parent_id: @group.id), class: 'btn btn-new pull-right' do
+ New Subgroup
+
+ - if @nested_groups.present?
+ %ul.content-list
+ = render partial: 'shared/groups/group', collection: @nested_groups, locals: { full_name: false }
+ - else
+ .nothing-here-block
+ There are no subgroups to show.
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index dd1df46792b..87f9b503989 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -528,7 +528,7 @@
- blob = Snippet.new(content: "Wow\nSuch\nFile")
.example
.file-holder
- .file-title
+ .js-file-title.file-title
Awesome file
.file-actions
.btn-group
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 60a561c9f9c..2c006e1712d 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -85,11 +85,17 @@
:javascript
- var date = $('#personal_access_token_expires_at').val();
-
- var datepicker = $(".datepicker").datepicker({
- dateFormat: "yy-mm-dd",
- minDate: 0
+ var $dateField = $('#personal_access_token_expires_at');
+ var date = $dateField.val();
+
+ new Pikaday({
+ field: $dateField.get(0),
+ theme: 'gitlab-theme',
+ format: 'YYYY-MM-DD',
+ minDate: new Date(),
+ onSelect: function(dateText) {
+ $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
});
$("#created-personal-access-token").click(function() {
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 1c3bccccb5c..a08436715d2 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -10,6 +10,7 @@
- if @project && event.project != @project
%span at
%strong= link_to_project event.project
+ = clipboard_button(clipboard_text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
#{time_ago_with_tooltip(event.created_at)}
.pull-right
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 23f54553014..8a40281e28c 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -7,7 +7,7 @@
#blob-content-holder.tree-holder
.file-holder
- .file-title
+ .js-file-title.file-title
= blob_icon @blob.mode, @blob.name
%strong
= @path
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index f75f438ee4f..19fa4c78501 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -24,7 +24,7 @@
#blob-content-holder.blob-content-holder
%article.file-holder
- .file-title
+ .js-file-title.file-title
= blob_icon blob.mode, blob.name
%strong
= blob.name
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 228ac61fc8c..e7adef5558a 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,5 +1,5 @@
.file-holder.file.append-bottom-default
- .file-title.clearfix
+ .js-file-title.file-title.clearfix
.editor-ref
= icon('code-fork')
= ref
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 05fe504d1c9..f5ca9607823 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -4,7 +4,7 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('boards')
- = page_specific_javascript_bundle_tag('boards_test') if Rails.env.test?
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index d94f23f5a38..08cb8a04413 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -22,9 +22,7 @@
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
- = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
- = icon('plus')
- Create Merge Request
+ = link_to "Create Merge Request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index d76d48187cd..08236216421 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -23,6 +23,4 @@
- if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
- = link_to create_mr_path, class: 'prepend-left-10 btn' do
- = icon("plus")
- Create Merge Request
+ = link_to "Create Merge Request", create_mr_path, class: 'prepend-left-10 btn'
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 75885badac9..0232a09b4a8 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,7 +1,8 @@
- environment = local_assigns.fetch(:environment, nil)
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) }
- .file-title
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
+ .js-file-title.file-title-flex-parent
+ .file-header-content
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
- unless diff_file.submodule?
.file-actions.hidden-xs
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index ddec775b789..5b09b6907ab 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -10,13 +10,13 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: old_path } }
= old_path
&rarr;
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: new_path } }
= new_path
- else
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path } }
= diff_file.new_path
- if diff_file.deleted_file
deleted
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index c2f4457b60b..5d4e593e4ef 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,7 +1,7 @@
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
#notes
= render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 085b2fc2814..5c9839cb330 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,60 +1,61 @@
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
- - if @bulk_edit
- .issue-check
- = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+ .issue-box
+ - if @bulk_edit
+ .issue-check
+ = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+ .issue-info-container
+ .issue-title.title
+ %span.issue-title-text
+ = confidential_icon(issue)
+ = link_to issue.title, issue_path(issue)
+ %ul.controls
+ - if issue.closed?
+ %li
+ CLOSED
- .issue-title.title
- %span.issue-title-text
- = confidential_icon(issue)
- = link_to issue.title, issue_path(issue)
- %ul.controls
- - if issue.closed?
- %li
- CLOSED
+ - if issue.assignee
+ %li
+ = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- - if issue.assignee
- %li
- = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
+ - upvotes, downvotes = issue.upvotes, issue.downvotes
+ - if upvotes > 0
+ %li
+ = icon('thumbs-up')
+ = upvotes
- - upvotes, downvotes = issue.upvotes, issue.downvotes
- - if upvotes > 0
- %li
- = icon('thumbs-up')
- = upvotes
+ - if downvotes > 0
+ %li
+ = icon('thumbs-down')
+ = downvotes
- - if downvotes > 0
- %li
- = icon('thumbs-down')
- = downvotes
+ - note_count = issue.notes.user.count
+ %li
+ = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
+ = icon('comments')
+ = note_count
- - note_count = issue.notes.user.count
- %li
- = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
- = icon('comments')
- = note_count
+ .issue-info
+ #{issuable_reference(issue)} &middot;
+ opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, issue.author, avatar: false)}
+ - if issue.milestone
+ &nbsp;
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+ = icon('clock-o')
+ = issue.milestone.title
+ - if issue.due_date
+ %span{ class: "#{'cred' if issue.overdue?}" }
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
+ - if issue.labels.any?
+ &nbsp;
+ - issue.labels.each do |label|
+ = link_to_label(label, subject: issue.project, css_class: 'label-link')
+ - if issue.tasks?
+ &nbsp;
+ %span.task-status
+ = issue.task_status
- .issue-info
- #{issuable_reference(issue)} &middot;
- opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
- by #{link_to_member(@project, issue.author, avatar: false)}
- - if issue.milestone
- &nbsp;
- = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
- = icon('clock-o')
- = issue.milestone.title
- - if issue.due_date
- %span{ class: "#{'cred' if issue.overdue?}" }
- &nbsp;
- = icon('calendar')
- = issue.due_date.to_s(:medium)
- - if issue.labels.any?
- &nbsp;
- - issue.labels.each do |label|
- = link_to_label(label, subject: issue.project, css_class: 'label-link')
- - if issue.tasks?
- &nbsp;
- %span.task-status
- = issue.task_status
-
- .pull-right.issue-updated-at
- %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ .pull-right.issue-updated-at
+ %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index a2305f4f547..d3eb3b7055b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -35,9 +35,9 @@
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if @issue.submittable_as_spam? && current_user.admin?
@@ -48,8 +48,8 @@
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, 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'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- if @issue.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 29f861c09c6..8d4a91cb64c 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,6 +3,9 @@
- hide_class = ''
= render "projects/issues/head"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
.top-area.adjust
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 4dbb97b3228..a5fbe9d6128 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -3,73 +3,74 @@
.issue-check
= check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
- .merge-request-title.title
- %span.merge-request-title-text
- = link_to merge_request.title, merge_request_path(merge_request)
- %ul.controls
- - if merge_request.merged?
- %li
- MERGED
- - elsif merge_request.closed?
- %li
- = icon('ban')
- CLOSED
+ .issue-info-container
+ .merge-request-title.title
+ %span.merge-request-title-text
+ = link_to merge_request.title, merge_request_path(merge_request)
+ %ul.controls
+ - if merge_request.merged?
+ %li
+ MERGED
+ - elsif merge_request.closed?
+ %li
+ = icon('ban')
+ CLOSED
- - if merge_request.head_pipeline
- %li
- = render_pipeline_status(merge_request.head_pipeline)
+ - if merge_request.head_pipeline
+ %li
+ = render_pipeline_status(merge_request.head_pipeline)
- - if merge_request.open? && merge_request.broken?
- %li
- = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
- = icon('exclamation-triangle')
+ - if merge_request.open? && merge_request.broken?
+ %li
+ = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
+ = icon('exclamation-triangle')
- - if merge_request.assignee
- %li
- = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
+ - if merge_request.assignee
+ %li
+ = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
- - if upvotes > 0
- %li
- = icon('thumbs-up')
- = upvotes
+ - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
+ - if upvotes > 0
+ %li
+ = icon('thumbs-up')
+ = upvotes
- - if downvotes > 0
- %li
- = icon('thumbs-down')
- = downvotes
+ - if downvotes > 0
+ %li
+ = icon('thumbs-down')
+ = downvotes
- - note_count = merge_request.related_notes.user.count
- %li
- = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
- = icon('comments')
- = note_count
+ - note_count = merge_request.related_notes.user.count
+ %li
+ = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
+ = icon('comments')
+ = note_count
- .merge-request-info
- #{issuable_reference(merge_request)} &middot;
- opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
- by #{link_to_member(@project, merge_request.author, avatar: false)}
- - if merge_request.target_project.default_branch != merge_request.target_branch
- &nbsp;
- = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
- = icon('code-fork')
- = merge_request.target_branch
+ .merge-request-info
+ #{issuable_reference(merge_request)} &middot;
+ opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, merge_request.author, avatar: false)}
+ - if merge_request.target_project.default_branch != merge_request.target_branch
+ &nbsp;
+ = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+ = icon('code-fork')
+ = merge_request.target_branch
- - if merge_request.milestone
- &nbsp;
- = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
- = icon('clock-o')
- = merge_request.milestone.title
+ - if merge_request.milestone
+ &nbsp;
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+ = icon('clock-o')
+ = merge_request.milestone.title
- - if merge_request.labels.any?
- &nbsp;
- - merge_request.labels.each do |label|
- = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+ - if merge_request.labels.any?
+ &nbsp;
+ - merge_request.labels.each do |label|
+ = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
- - if merge_request.tasks?
- &nbsp;
- %span.task-status
- = merge_request.task_status
+ - if merge_request.tasks?
+ &nbsp;
+ %span.task-status
+ = merge_request.task_status
- .pull-right.hidden-xs
- %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
+ .pull-right.hidden-xs
+ %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 83250443bea..dd615d3036c 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -3,10 +3,9 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_vue')
= page_specific_javascript_bundle_tag('diff_notes')
-.merge-request{ 'data-url' => merge_request_path(@merge_request) }
+.merge-request{ 'data-url' => merge_request_path(@merge_request), 'data-project-path' => project_path(@merge_request.project) }
= render "projects/merge_requests/show/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index dcf578b85f9..1ecd9924d88 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -23,7 +23,7 @@
.files-wrapper{ "v-if" => "!isLoading && !hasError" }
.files
.diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
- .file-title
+ .js-file-title.file-title
%i.fa.fa-fw{ ":class" => "file.iconClass" }
%strong {{file.filePath}}
= render partial: 'projects/merge_requests/conflicts/file_actions'
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index c3a6096aa54..06a31698ee6 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -3,6 +3,9 @@
- page_description @milestone.description
= render "projects/issues/head"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
%div{ class: container_class }
.detail-page-header.milestone-page-header
.status-box{ class: status_box_class(@milestone) }
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 4b1da9c73e5..e58de9f0e18 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -30,8 +30,7 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
- %resolve-btn{ "project-path" => "#{project_path(note.project)}",
- "discussion-id" => "#{note.discussion_id}",
+ %resolve-btn{ "discussion-id" => "#{note.discussion_id}",
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 485b23815bc..6b3d7d4008b 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -4,7 +4,7 @@
.project-snippets
%article.file-holder.snippet-file-content
- .file-title
+ .js-file-title.file-title
= blob_icon 0, @snippet.file_name
= @snippet.file_name
.file-actions
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index a1f4e3e8ed6..bdcc160a067 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
%article.file-holder.readme-holder
- .file-title
+ .js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
%strong
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 9e8adc82583..7f1f807e2e7 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,7 +1,7 @@
- file_name, blob = blob
.blob-result
.file-holder
- .file-title
+ .js-file-title.file-title
- ref = @search_results.repository_ref
- blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
= link_to blob_link do
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 23ca6479414..f7808ea6aff 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -14,7 +14,7 @@
- snippet_path = reliable_snippet_path(snippet)
= link_to snippet_path do
.file-holder
- .file-title
+ .js-file-title.file-title
%i.fa.fa-file
%strong= snippet.file_name
- if markup?(snippet.file_name)
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 648d0bd76cb..d87f9df2677 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,7 +1,7 @@
- wiki_blob = parse_search_result(wiki_blob)
.blob-result
.file-holder
- .file-title
+ .js-file-title.file-title
= link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do
%i.fa.fa-file
%strong
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 0bc851b4256..efb207b9916 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,3 +1,4 @@
+- parent = Group.find_by(id: params[:parent_id] || @group.parent_id)
- if @group.persisted?
.form-group
= f.label :name, class: 'control-label' do
@@ -11,11 +12,15 @@
.col-sm-10
.input-group.gl-field-error-anchor
.input-group-addon
- = root_url
+ %span>= root_url
+ - if parent
+ %strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE,
title: 'Please choose a group name with no special characters.'
+ - if parent
+ = f.hidden_field :parent_id, value: parent.id
- if @group.persisted?
.alert.alert-warning.prepend-top-10
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index dd9e433491b..60ca23ef680 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,4 +1,5 @@
- group_member = local_assigns[:group_member]
+- full_name = true unless local_assigns[:full_name] == false
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if group.description.blank?
@@ -28,7 +29,10 @@
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to group, class: 'group-name' do
- = group.full_name
+ - if full_name
+ = group.full_name
+ - else
+ = group.name
- if group_member
as
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 173fa922f56..6e417aa2251 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -11,7 +11,7 @@
class: "check_all_issues left"
.issues-other-filters.filtered-search-container
.filtered-search-input-container
- %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]) }
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]), 'data-base-endpoint' => namespace_project_path(@project.namespace, @project) }
= icon('filter')
%button.clear-search.hidden{ type: 'button' }
= icon('times')
diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml
index 748b10a1298..ed94773ef89 100644
--- a/app/views/shared/milestones/_form_dates.html.haml
+++ b/app/views/shared/milestones/_form_dates.html.haml
@@ -10,6 +10,3 @@
.col-sm-10
= f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
%a.inline.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date
-
-:javascript
- new gl.DueDateSelectors();
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 28935c8b598..4c7d69d40d5 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -5,7 +5,7 @@
- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable)
-%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'ui-sort-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(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) }
%span
- if show_project_name
%strong #{project.name} &middot;
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index ac028f18e50..c19697802ce 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,6 +1,7 @@
- @sort ||= sort_value_recently_updated
- personal = params[:personal]
- archived = params[:archived]
+- shared = params[:shared]
- namespace_id = params[:namespace_id]
.dropdown
- toggle_text = projects_sort_options_hash[@sort]
@@ -28,3 +29,14 @@
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do
Owned by me
+ - if @group && @group.shared_projects.present?
+ %li.divider
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: nil), class: ("is-active" unless shared.present?) do
+ All projects
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 0), class: ("is-active" if shared == '0') do
+ Hide shared projects
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 1), class: ("is-active" if shared == '1') do
+ Hide group projects
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 56c0f7390a5..e7f7db73223 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -18,7 +18,7 @@
= f.label :file_name, "File", class: 'control-label'
.col-sm-10
.file-holder.snippet
- .file-title
+ .js-file-title.file-title
= f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
index 92151176fce..1a6e2542dc1 100644
--- a/app/views/sherlock/file_samples/show.html.haml
+++ b/app/views/sherlock/file_samples/show.html.haml
@@ -26,7 +26,7 @@
= @file_sample.events
%article.file-holder
- .file-title
+ .js-file-title.file-title
%i.fa.fa-file-text-o.fa-fw
%strong
= @file_sample.file
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 837a1a0cc8c..970afbe6b64 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,7 @@
= render 'shared/snippets/header'
%article.file-holder.snippet-file-content
- .file-title
+ .js-file-title.file-title
= blob_icon 0, @snippet.file_name
= @snippet.file_name
.file-actions
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index 3194c389b3d..5483bbb210b 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -6,6 +6,6 @@ class DeleteUserWorker
delete_user = User.find(delete_user_id)
current_user = User.find(current_user_id)
- DeleteUserService.new(current_user).execute(delete_user, options.symbolize_keys)
+ Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys)
end
end
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
index a49a5fd0855..07e82767b06 100644
--- a/app/workers/group_destroy_worker.rb
+++ b/app/workers/group_destroy_worker.rb
@@ -11,6 +11,6 @@ class GroupDestroyWorker
user = User.find(user_id)
- DestroyGroupService.new(group, user).execute
+ Groups::DestroyService.new(group, user).execute
end
end
diff --git a/changelogs/unreleased/20495-plus-icon-button.yml b/changelogs/unreleased/20495-plus-icon-button.yml
new file mode 100644
index 00000000000..0f8650eb7b6
--- /dev/null
+++ b/changelogs/unreleased/20495-plus-icon-button.yml
@@ -0,0 +1,4 @@
+---
+title: Remove plus icon from MR button on compare view
+merge_request:
+author:
diff --git a/changelogs/unreleased/23104-remove-public-param-for-projects.yml b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
new file mode 100644
index 00000000000..78eb785279f
--- /dev/null
+++ b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: remove `public` param for projects'
+merge_request: 8736
+author:
diff --git a/changelogs/unreleased/25709-diff-file-overflow.yml b/changelogs/unreleased/25709-diff-file-overflow.yml
new file mode 100644
index 00000000000..7d1b2b36ab8
--- /dev/null
+++ b/changelogs/unreleased/25709-diff-file-overflow.yml
@@ -0,0 +1,4 @@
+---
+title: Responsive title in diffs inline, side by side, with and without sidebar
+merge_request: 8475
+author:
diff --git a/changelogs/unreleased/26705-filter-todos-by-manual-add.yml b/changelogs/unreleased/26705-filter-todos-by-manual-add.yml
new file mode 100644
index 00000000000..3521496a20e
--- /dev/null
+++ b/changelogs/unreleased/26705-filter-todos-by-manual-add.yml
@@ -0,0 +1,4 @@
+---
+title: Filter todos by manual add
+merge_request: 8691
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/27610-issue-number-alignment.yml b/changelogs/unreleased/27610-issue-number-alignment.yml
new file mode 100644
index 00000000000..19ab8872c62
--- /dev/null
+++ b/changelogs/unreleased/27610-issue-number-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: fixes issue number alignment problem in MR and issue list
+merge_request: 9020
+author:
diff --git a/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml b/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml
new file mode 100644
index 00000000000..6c98b46d8cb
--- /dev/null
+++ b/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes dropdown width in admin project page
+merge_request: 9002
+author:
diff --git a/changelogs/unreleased/27880-pipelines-table-not-showing-commit-branch.yml b/changelogs/unreleased/27880-pipelines-table-not-showing-commit-branch.yml
new file mode 100644
index 00000000000..4251754618b
--- /dev/null
+++ b/changelogs/unreleased/27880-pipelines-table-not-showing-commit-branch.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes Pipelines table is not showing branch name for commit
+merge_request:
+author:
diff --git a/changelogs/unreleased/27943-contribution-list-on-profile-page-is-aligned-right.yml b/changelogs/unreleased/27943-contribution-list-on-profile-page-is-aligned-right.yml
new file mode 100644
index 00000000000..fcbd48b0357
--- /dev/null
+++ b/changelogs/unreleased/27943-contribution-list-on-profile-page-is-aligned-right.yml
@@ -0,0 +1,4 @@
+---
+title: Fix contribution activity alignment
+merge_request:
+author:
diff --git a/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml b/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml
new file mode 100644
index 00000000000..f335ae27fda
--- /dev/null
+++ b/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml
@@ -0,0 +1,4 @@
+---
+title: Bypass email domain validation when a user is created by an admin.
+merge_request: 8575
+author: Reza Mohammadi @remohammadi
diff --git a/changelogs/unreleased/copy-branch-to-clipboard.yml b/changelogs/unreleased/copy-branch-to-clipboard.yml
new file mode 100644
index 00000000000..c12e324ed3c
--- /dev/null
+++ b/changelogs/unreleased/copy-branch-to-clipboard.yml
@@ -0,0 +1,4 @@
+---
+title: Added the ability to copy a branch name to the clipboard
+merge_request: 9103
+author: Glenn Sayers
diff --git a/changelogs/unreleased/dz-create-nested-groups-via-ui.yml b/changelogs/unreleased/dz-create-nested-groups-via-ui.yml
new file mode 100644
index 00000000000..f9529a5941a
--- /dev/null
+++ b/changelogs/unreleased/dz-create-nested-groups-via-ui.yml
@@ -0,0 +1,4 @@
+---
+title: Allow creating nested groups via UI
+merge_request: 8786
+author:
diff --git a/changelogs/unreleased/dz-nested-groups-api.yml b/changelogs/unreleased/dz-nested-groups-api.yml
new file mode 100644
index 00000000000..d33ff42700f
--- /dev/null
+++ b/changelogs/unreleased/dz-nested-groups-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add nested groups to the API
+merge_request: 9034
+author:
diff --git a/changelogs/unreleased/dz-refactor-full-path.yml b/changelogs/unreleased/dz-refactor-full-path.yml
new file mode 100644
index 00000000000..da8568fd220
--- /dev/null
+++ b/changelogs/unreleased/dz-refactor-full-path.yml
@@ -0,0 +1,4 @@
+---
+title: Store group and project full name and full path in routes table
+merge_request: 8979
+author:
diff --git a/changelogs/unreleased/fixes-namespace-api-documentation.yml b/changelogs/unreleased/fixes-namespace-api-documentation.yml
new file mode 100644
index 00000000000..6b578bb1602
--- /dev/null
+++ b/changelogs/unreleased/fixes-namespace-api-documentation.yml
@@ -0,0 +1,4 @@
+---
+title: Update API docs for new namespace format
+merge_request: 9073
+author: Markus Koller
diff --git a/changelogs/unreleased/issue_26701.yml b/changelogs/unreleased/issue_26701.yml
new file mode 100644
index 00000000000..6834351bf43
--- /dev/null
+++ b/changelogs/unreleased/issue_26701.yml
@@ -0,0 +1,4 @@
+---
+title: Remove JIRA closed status icon
+merge_request:
+author:
diff --git a/changelogs/unreleased/lfs-noauth-public-repo.yml b/changelogs/unreleased/lfs-noauth-public-repo.yml
new file mode 100644
index 00000000000..60f62d7691b
--- /dev/null
+++ b/changelogs/unreleased/lfs-noauth-public-repo.yml
@@ -0,0 +1,4 @@
+---
+title: Support unauthenticated LFS object downloads for public projects
+merge_request: 8824
+author: Ben Boeckel
diff --git a/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml b/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml
new file mode 100644
index 00000000000..0751047c3c0
--- /dev/null
+++ b/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml
@@ -0,0 +1,4 @@
+---
+title: pass in current_user in MergeRequest and MergeRequestsHelper
+merge_request: 8624
+author: Dongqing Hu
diff --git a/changelogs/unreleased/protected-branch-dropdown-titles.yml b/changelogs/unreleased/protected-branch-dropdown-titles.yml
new file mode 100644
index 00000000000..df82cc00fc9
--- /dev/null
+++ b/changelogs/unreleased/protected-branch-dropdown-titles.yml
@@ -0,0 +1,4 @@
+---
+title: Added headers to protected branch access dropdowns
+merge_request:
+author:
diff --git a/changelogs/unreleased/removal_of_unused_parameter.yml b/changelogs/unreleased/removal_of_unused_parameter.yml
new file mode 100644
index 00000000000..26bffafd9d9
--- /dev/null
+++ b/changelogs/unreleased/removal_of_unused_parameter.yml
@@ -0,0 +1,4 @@
+---
+title: 'removed unused parameter ''status_only: true'''
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-jquery-ui-datepicker.yml b/changelogs/unreleased/remove-jquery-ui-datepicker.yml
new file mode 100644
index 00000000000..cd00690d774
--- /dev/null
+++ b/changelogs/unreleased/remove-jquery-ui-datepicker.yml
@@ -0,0 +1,4 @@
+---
+title: Replaced jQuery UI datepicker
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-jquery-ui-sortable.yml b/changelogs/unreleased/remove-jquery-ui-sortable.yml
new file mode 100644
index 00000000000..35f47822738
--- /dev/null
+++ b/changelogs/unreleased/remove-jquery-ui-sortable.yml
@@ -0,0 +1,4 @@
+---
+title: Replaced jQuery UI sortable
+merge_request:
+author:
diff --git a/changelogs/unreleased/rename_delete_services.yml b/changelogs/unreleased/rename_delete_services.yml
new file mode 100644
index 00000000000..686a1ef3d55
--- /dev/null
+++ b/changelogs/unreleased/rename_delete_services.yml
@@ -0,0 +1,4 @@
+---
+title: Fix inconsistent naming for services that delete things
+merge_request: 5803
+author: dixpac
diff --git a/changelogs/unreleased/upgrade-babel-v6.yml b/changelogs/unreleased/upgrade-babel-v6.yml
new file mode 100644
index 00000000000..55f9b3e407c
--- /dev/null
+++ b/changelogs/unreleased/upgrade-babel-v6.yml
@@ -0,0 +1,4 @@
+---
+title: upgrade babel 5.8.x to babel 6.22.x
+merge_request: 9072
+author:
diff --git a/changelogs/unreleased/upgrade-webpack-v2-2.yml b/changelogs/unreleased/upgrade-webpack-v2-2.yml
new file mode 100644
index 00000000000..6a49859d68c
--- /dev/null
+++ b/changelogs/unreleased/upgrade-webpack-v2-2.yml
@@ -0,0 +1,4 @@
+---
+title: upgrade to webpack v2.2
+merge_request: 9078
+author:
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index aabe859730a..7336d7c842a 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -302,3 +302,21 @@
:why: https://github.com/dchest/tweetnacl-js/blob/master/LICENSE
:versions: []
:when: 2017-01-14 20:10:57.812077000 Z
+- - :approve
+ - wordwrap
+ - :who: Mike Greiling
+ :why: https://github.com/substack/node-wordwrap/blob/0.0.3/LICENSE
+ :versions: []
+ :when: 2017-02-08 20:17:13.084968000 Z
+- - :approve
+ - spdx-expression-parse
+ - :who: Mike Greiling
+ :why: https://github.com/kemitchell/spdx-expression-parse.js/blob/v1.0.4/LICENSE
+ :versions: []
+ :when: 2017-02-08 22:33:01.806977000 Z
+- - :approve
+ - spdx-license-ids
+ - :who: Mike Greiling
+ :why: https://github.com/shinnn/spdx-license-ids/blob/v1.2.2/LICENSE
+ :versions: []
+ :when: 2017-02-08 22:35:00.225232000 Z
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 60a1175fe80..73f69d76995 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -25,5 +25,6 @@ scope(path: 'groups/*id',
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
get :activity, as: :activity_group
+ get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 953ae463c86..00f448c1fbb 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -17,7 +17,7 @@ var config = {
application: './application.js',
blob_edit: './blob_edit/blob_edit_bundle.js',
boards: './boards/boards_bundle.js',
- boards_test: './boards/test_utils/simulate_drag.js',
+ simulate_drag: './test_utils/simulate_drag.js',
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js',
@@ -48,26 +48,23 @@ var config = {
devtool: 'inline-source-map',
module: {
- loaders: [
+ rules: [
{
test: /\.(js|es6)$/,
exclude: /(node_modules|vendor\/assets)/,
loader: 'babel-loader',
- query: {
- // 'use strict' was broken in sprockets-es6 due to sprockets concatination method.
- // many es5 strict errors which were never caught ended up in our es6 assets as a result.
- // this hack is necessary until they can be fixed.
- blacklist: ['useStrict']
+ options: {
+ presets: [
+ ["es2015", {"modules": false}],
+ 'stage-2'
+ ]
}
},
{
test: /\.(js|es6)$/,
+ exclude: /node_modules/,
loader: 'imports-loader',
- query: 'this=>window'
- },
- {
- test: /\.json$/,
- loader: 'json-loader'
+ options: 'this=>window'
}
]
},
@@ -88,7 +85,7 @@ var config = {
],
resolve: {
- extensions: ['', '.js', '.es6', '.js.es6'],
+ extensions: ['.js', '.es6', '.js.es6'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap',
@@ -104,14 +101,16 @@ if (IS_PRODUCTION) {
config.devtool = 'source-map';
config.plugins.push(
new webpack.NoErrorsPlugin(),
+ new webpack.LoaderOptionsPlugin({
+ minimize: true,
+ debug: false
+ }),
new webpack.optimize.UglifyJsPlugin({
- compress: { warnings: false }
+ sourceMap: true
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify('production') }
- }),
- new webpack.optimize.DedupePlugin(),
- new webpack.optimize.OccurrenceOrderPlugin()
+ })
);
}
diff --git a/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
index 15ad8e8bcbb..ac50035eba4 100644
--- a/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
+++ b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
@@ -1,9 +1,15 @@
class AddDevelopersCanMergeToProtectedBranches < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
- def change
+ def up
add_column_with_default :protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false
end
+
+ def down
+ remove_column :protected_branches, :developers_can_merge
+ end
end
diff --git a/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
index 296f1dfac7b..20a77000ba8 100644
--- a/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
+++ b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
@@ -14,7 +14,11 @@ class AddSubmittedAsHamToSpamLogs < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_column_with_default :spam_logs, :submitted_as_ham, :boolean, default: false
end
+
+ def down
+ remove_column :spam_logs, :submitted_as_ham
+ end
end
diff --git a/db/migrate/20170204172458_add_name_to_route.rb b/db/migrate/20170204172458_add_name_to_route.rb
new file mode 100644
index 00000000000..38ed1ad9039
--- /dev/null
+++ b/db/migrate/20170204172458_add_name_to_route.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddNameToRoute < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :routes, :name, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 850772ba356..3fef5b82073 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1037,6 +1037,7 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.string "path", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "name"
end
add_index "routes", ["path"], name: "index_routes_on_path", unique: true, using: :btree
diff --git a/doc/README.md b/doc/README.md
index 78030ce4e72..1943d656aa7 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -56,7 +56,7 @@
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Pages configuration](administration/pages/index.md) Configure GitLab Pages.
- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
-- [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics.
+- [GitLab performance monitoring with Prometheus](administration/monitoring/prometheus/index.md) Configure GitLab and Prometheus for measuring performance metrics.
- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md
index 8b106e89cc2..17c2b4b70d3 100644
--- a/doc/administration/monitoring/performance/introduction.md
+++ b/doc/administration/monitoring/performance/introduction.md
@@ -15,7 +15,7 @@ documents in order to understand and properly configure GitLab Performance Monit
>**Note:**
Omnibus GitLab 8.16 includes Prometheus as an additional tool to collect
metrics. It will eventually replace InfluxDB when their metrics collection is
-on par. Read more in the [Prometheus documentation](prometheus.md).
+on par. Read more in the [Prometheus documentation](../prometheus/index.md).
## Introduction to GitLab Performance Monitoring
diff --git a/doc/administration/monitoring/performance/prometheus.md b/doc/administration/monitoring/performance/prometheus.md
index 51c63325064..d73ef5d1789 100644
--- a/doc/administration/monitoring/performance/prometheus.md
+++ b/doc/administration/monitoring/performance/prometheus.md
@@ -1,102 +1 @@
-# GitLab Prometheus
-
->**Notes:**
-- Prometheus and the node exporter are bundled in the Omnibus GitLab package
- since GitLab 8.16. For installations from source you will have to install
- them yourself. Over subsequent releases additional GitLab metrics will be
- captured.
-- Prometheus services are off by default but will be on starting with GitLab 9.0.
-
-[Prometheus] is a powerful time-series monitoring service, providing a flexible
-platform for monitoring GitLab and other software products.
-GitLab provides out of the box monitoring with Prometheus, providing easy
-access to high quality time-series monitoring of GitLab services.
-
-## Overview
-
-Prometheus works by periodically connecting to data sources and collecting their
-performance metrics. To view and work with the monitoring data, you can either
-connect directly to Prometheus or utilize a dashboard tool like [Grafana].
-
-## Configuring Prometheus
-
->**Note:**
-Available since Omnibus GitLab 8.16. For installations from source you'll
-have to install and configure it yourself.
-
-To enable Prometheus:
-
-1. Edit `/etc/gitlab/gitlab.rb`
-1. Find and uncomment the following line, making sure it's set to `true`:
-
- ```ruby
- prometheus['enable'] = true
- ```
-
-1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
- take effect
-
-By default, Prometheus will run as the `gitlab-prometheus` user and listen on
-TCP port `9090` under localhost. If the [node exporter](#node-exporter) service
-has been enabled, it will automatically be set up as a monitoring target for
-Prometheus.
-
-## Viewing Performance Metrics
-
-After you have [enabled Prometheus](#configuring-prometheus), you can visit
-`<your_domain_name>:9090` for the dashboard that Prometheus offers by default.
-
-The performance data collected by Prometheus can be viewed directly in the
-Prometheus console or through a compatible dashboard tool.
-The Prometheus interface provides a [flexible query language][prom-query] to work
-with the collected data where you can visualize their output.
-For a more fully featured dashboard, Grafana can be used and has
-[official support for Prometheus][prom-grafana].
-
-## Prometheus exporters
-
-There are a number of libraries and servers which help in exporting existing
-metrics from third-party systems as Prometheus metrics. This is useful for cases
-where it is not feasible to instrument a given system with Prometheus metrics
-directly (for example, HAProxy or Linux system stats). You can read more in the
-[Prometheus exporters and integrations documentation][prom-exporters].
-
-While you can use any exporter you like with your GitLab installation, the
-following ones documented here are bundled in the Omnibus GitLab packages
-making it easy to configure and use.
-
-### Node exporter
-
->**Note:**
-Available since Omnibus GitLab 8.16. For installations from source you'll
-have to install and configure it yourself.
-
-The [node exporter] allows you to measure various machine resources such as
-memory, disk and CPU utilization.
-
-To enable the node exporter:
-
-1. [Enable Prometheus](#configuring-prometheus)
-1. Edit `/etc/gitlab/gitlab.rb`
-1. Find and uncomment the following line, making sure it's set to `true`:
-
- ```ruby
- node_exporter['enable'] = true
- ```
-
-1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
- take effect
-
-Prometheus it will now automatically begin collecting performance data from
-the node exporter. You can visit `<your_domain_name>:9100/metrics` for a real
-time representation of the metrics that are collected. Refresh the page and
-you will see the data change.
-
-[grafana]: https://grafana.net
-[node exporter]: https://github.com/prometheus/node_exporter
-[prometheus]: https://prometheus.io
-[prom-query]: https://prometheus.io/docs/querying/basics
-[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
-[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
-[prom-exporters]: https://prometheus.io/docs/instrumenting/exporters/
-[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
+This document was moved to [monitoring/prometheus](../prometheus/index.md).
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
new file mode 100644
index 00000000000..86ef9d167e2
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -0,0 +1,30 @@
+# GitLab monitor exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1132]. For installations from source
+you'll have to install and configure it yourself.
+
+The [GitLab monitor exporter] allows you to measure various GitLab metrics.
+
+To enable the GitLab monitor exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ gitlab_monitor_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the GitLab monitor exporter exposed under `localhost:9168`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1132]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1132
+[GitLab monitor exporter]: https://gitlab.com/gitlab-org/gitlab-monitor
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
new file mode 100644
index 00000000000..3a394c561db
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -0,0 +1,147 @@
+# GitLab Prometheus
+
+>**Notes:**
+- Prometheus and the various exporters listed in this page are bundled in the
+ Omnibus GitLab package. Check each exporter's documentation for the timeline
+ they got added. For installations from source you will have to install
+ them yourself. Over subsequent releases additional GitLab metrics will be
+ captured.
+- Prometheus services are off by default but will be on starting with GitLab 9.0.
+- Prometheus and its exporters do not authenticate users, and will be available
+ to anyone who can access them.
+
+[Prometheus] is a powerful time-series monitoring service, providing a flexible
+platform for monitoring GitLab and other software products.
+GitLab provides out of the box monitoring with Prometheus, providing easy
+access to high quality time-series monitoring of GitLab services.
+
+## Overview
+
+Prometheus works by periodically connecting to data sources and collecting their
+performance metrics via the [various exporters](#prometheus-exporters). To view
+and work with the monitoring data, you can either
+[connect directly to Prometheus](#viewing-performance-metrics) or utilize a
+dashboard tool like [Grafana].
+
+## Configuring Prometheus
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+To enable Prometheus:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ prometheus['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+By default, Prometheus will run as the `gitlab-prometheus` user and listen on
+`http://localhost:9090`. If the [node exporter](#node-exporter) service
+has been enabled, it will automatically be set up as a monitoring target for
+Prometheus.
+
+## Changing the port Prometheus listens on
+
+>**Note:**
+The following change was added in [GitLab Omnibus 8.17][1261]. Although possible,
+it's not recommended to change the default address and port Prometheus listens
+on as this might affect or conflict with other services running on the GitLab
+server. Proceed at your own risk.
+
+To change the address/port that Prometheus listens on:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line:
+
+ ```ruby
+ prometheus['listen_address'] = 'localhost:9090'
+ ```
+
+ Replace `localhost:9090` with the address/port you want Prometheus to
+ listen on.
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+## Viewing performance metrics
+
+After you have [enabled Prometheus](#configuring-prometheus), you can visit
+`http://localhost:9090` for the dashboard that Prometheus offers by default.
+
+>**Note:**
+If SSL has been enabled on your GitLab instance, you may not be able to access
+Prometheus on the same browser as GitLab due to [HSTS][hsts]. We plan to
+[provide access via GitLab][multi-user-prometheus], but in the interim there are
+some workarounds: using a separate browser for Prometheus, resetting HSTS, or
+having [Nginx proxy it][nginx-custom-config]. Follow issue [#27069] for more
+information.
+
+The performance data collected by Prometheus can be viewed directly in the
+Prometheus console or through a compatible dashboard tool.
+The Prometheus interface provides a [flexible query language][prom-query] to work
+with the collected data where you can visualize their output.
+For a more fully featured dashboard, Grafana can be used and has
+[official support for Prometheus][prom-grafana].
+
+Sample Prometheus queries:
+
+- **% Memory used:** `(1 - ((node_memory_MemFree + node_memory_Cached) / node_memory_MemTotal)) * 100`
+- **% CPU load:** `1 - rate(node_cpu{mode="idle"}[5m])`
+- **Data transmitted:** `irate(node_network_transmit_bytes[5m])`
+- **Data received:** `irate(node_network_receive_bytes[5m])`
+
+## Prometheus exporters
+
+There are a number of libraries and servers which help in exporting existing
+metrics from third-party systems as Prometheus metrics. This is useful for cases
+where it is not feasible to instrument a given system with Prometheus metrics
+directly (for example, HAProxy or Linux system stats). You can read more in the
+[Prometheus exporters and integrations upstream documentation][prom-exporters].
+
+While you can use any exporter you like with your GitLab installation, the
+following ones documented here are bundled in the Omnibus GitLab packages
+making it easy to configure and use.
+
+### Node exporter
+
+The node exporter allows you to measure various machine resources such as
+memory, disk and CPU utilization.
+
+[➔ Read more about the node exporter.](node_exporter.md)
+
+### Redis exporter
+
+The Redis exporter allows you to measure various Redis metrics.
+
+[➔ Read more about the Redis exporter.](redis_exporter.md)
+
+### Postgres exporter
+
+The Postgres exporter allows you to measure various PostgreSQL metrics.
+
+[➔ Read more about the Postgres exporter.](postgres_exporter.md)
+
+### GitLab monitor exporter
+
+The GitLab monitor exporter allows you to measure various GitLab metrics.
+
+[➔ Read more about the GitLab monitor exporter.](gitlab_monitor_exporter.md)
+
+[grafana]: https://grafana.net
+[hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
+[multi-user-prometheus]: https://gitlab.com/gitlab-org/multi-user-prometheus
+[nginx-custom-config]: https://docs.gitlab.com/omnibus/settings/nginx.html#inserting-custom-nginx-settings-into-the-gitlab-server-block
+[prometheus]: https://prometheus.io
+[prom-exporters]: https://prometheus.io/docs/instrumenting/exporters/
+[prom-query]: https://prometheus.io/docs/querying/basics
+[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
+[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
+[#27069]: https://gitlab.com/gitlab-org/gitlab-ce/issues/27069
+[1261]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1261
diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md
new file mode 100644
index 00000000000..aef7758a88f
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/node_exporter.md
@@ -0,0 +1,30 @@
+# Node exporter
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+The [node exporter] allows you to measure various machine resources such as
+memory, disk and CPU utilization.
+
+To enable the node exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ node_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the node exporter exposed under `localhost:9100`.
+
+[← Back to the main Prometheus page](index.md)
+
+[node exporter]: https://github.com/prometheus/node_exporter
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md
new file mode 100644
index 00000000000..8e2d3162f88
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/postgres_exporter.md
@@ -0,0 +1,30 @@
+# Postgres exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1131]. For installations from source
+you'll have to install and configure it yourself.
+
+The [postgres exporter] allows you to measure various PostgreSQL metrics.
+
+To enable the postgres exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ postgres_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the postgres exporter exposed under `localhost:9187`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1131]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1131
+[postgres exporter]: https://github.com/wrouesnel/postgres_exporter
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md
new file mode 100644
index 00000000000..d54d409dbb6
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/redis_exporter.md
@@ -0,0 +1,33 @@
+# Redis exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1118]. For installations from source
+you'll have to install and configure it yourself.
+
+The [Redis exporter] allows you to measure various [Redis] metrics. For more
+information on what's exported [read the upstream documentation][redis-exp].
+
+To enable the Redis exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ redis_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the Redis exporter exposed under `localhost:9121`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1118]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1118
+[redis]: https://redis.io
+[redis exporter]: https://github.com/oliver006/redis_exporter
+[redis-exp]: https://github.com/oliver006/redis_exporter/blob/master/README.md#whats-exported
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 3b38e3e1bee..a3a43ca7f1c 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -32,7 +32,8 @@ GET /groups
"web_url": "http://localhost:3000/groups/foo-bar",
"request_access_enabled": false,
"full_name": "Foobar Group",
- "full_path": "foo-bar"
+ "full_path": "foo-bar",
+ "parent_id": null
}
]
```
@@ -105,15 +106,7 @@ Example response:
"id": 5,
"name": "Experimental",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
+ "kind": "group"
},
"avatar_url": null,
"star_count": 1,
@@ -156,8 +149,9 @@ Example response:
"avatar_url": null,
"web_url": "https://gitlab.example.com/groups/twitter",
"request_access_enabled": false,
- "full_name": "Foobar Group",
- "full_path": "foo-bar",
+ "full_name": "Twitter",
+ "full_path": "twitter",
+ "parent_id": null,
"projects": [
{
"id": 7,
@@ -188,15 +182,7 @@ Example response:
"id": 4,
"name": "Twitter",
"path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -235,15 +221,7 @@ Example response:
"id": 4,
"name": "Twitter",
"path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -284,15 +262,7 @@ Example response:
"id": 5,
"name": "H5bp",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:26.621Z",
- "updated_at": "2016-06-17T07:47:26.621Z",
- "description": "Id consequatur rem vel qui doloremque saepe.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -332,6 +302,7 @@ Parameters:
- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group
- `request_access_enabled` (optional) - Allow users to request member access.
+- `parent_id` (optional) - The parent group id for creating nested group.
## Transfer project to group
@@ -383,6 +354,7 @@ Example response:
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "parent_id": null,
"projects": [
{
"id": 9,
@@ -412,15 +384,7 @@ Example response:
"id": 5,
"name": "Experimental",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
+ "kind": "group"
},
"avatar_url": null,
"star_count": 1,
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 5ef5e3f5744..eab532af594 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -57,7 +57,7 @@ Once you have the authorization code you can request an `access_token` using the
```
parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
-RestClient.post 'http://localhost:3000/oauth/token', parameters
+RestClient.post 'http://gitlab.example.com/oauth/token', parameters
# The response will be
{
@@ -77,13 +77,13 @@ You can now make requests to the API with the access token returned.
The access token allows you to make requests to the API on a behalf of a user.
```
-GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+GET https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN
```
Or you can put the token to the Authorization header:
```
-curl --header "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/user
```
## Resource Owner Password Credentials
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 122075bbd11..bad238f57d7 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -72,13 +72,10 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
@@ -125,13 +122,10 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 4,
"name": "Brightbox",
- "owner_id": 1,
"path": "brightbox",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"permissions": {
"project_access": {
@@ -210,13 +204,10 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
@@ -260,13 +251,10 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 4,
"name": "Brightbox",
- "owner_id": 1,
"path": "brightbox",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"permissions": {
"project_access": {
@@ -398,13 +386,10 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"permissions": {
"project_access": {
@@ -642,7 +627,6 @@ 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 |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -676,7 +660,6 @@ 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 |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -709,7 +692,6 @@ 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 |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -782,13 +764,10 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
@@ -850,13 +829,10 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
@@ -924,13 +900,10 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"permissions": {
"project_access": {
@@ -1009,13 +982,10 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group"
},
"permissions": {
"project_access": {
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 73dde599b7e..dbb3c1113e8 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -113,7 +113,7 @@ DELETE /projects/:id/repository/files
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
```
Example response:
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index 3b075ff5fc0..2a531193adf 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -25,6 +25,8 @@ git clone PASTE HTTPS OR SSH HERE
A clone of the project will be created in your computer.
+>**Note:** If you clone your project via an URL that contains special characters, make sure that they are URL-encoded.
+
### Go into a project, directory or file to work in it
```
diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png
index 26df4c0a734..aaa1a39313d 100644
--- a/doc/gitlab-basics/img/profile_settings.png
+++ b/doc/gitlab-basics/img/profile_settings.png
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
index 6a1430d9663..7ebb8973ef0 100644
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
+++ b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
Binary files differ
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index e5247082a89..212b4854dd7 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -80,10 +80,13 @@ from step 5.
1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console
page from step 5.
-1. Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
-for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be an Auth0 icon below the regular sign in
form. Click the icon to begin the authentication process. Auth0 will ask the
user to sign in and authorize the GitLab application. If everything goes well
the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
index 48dddf7df44..5e3e9f5ab77 100644
--- a/doc/integration/azure.md
+++ b/doc/integration/azure.md
@@ -78,6 +78,10 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Microsoft icon below the regular sign in form. Click the icon to begin the authentication process. Microsoft will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index e34e306f9ac..f757edf0bc2 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -58,8 +58,11 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Save the configuration file.
-1. Run `gitlab-ctl reconfigure` for the omnibus package.
-
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a CAS tab in the sign in form.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
+
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index 40d93aef2a9..f8370cd349e 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -53,6 +53,11 @@ To enable the Crowd OmniAuth provider you must register your application with Cr
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
+
+On the sign in page there should now be a Crowd tab in the sign in form.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
-On the sign in page there should now be a Crowd tab in the sign in form. \ No newline at end of file
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index 77bb75cbfca..a67de23b17b 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -92,6 +92,10 @@ something else descriptive.
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Facebook icon below the regular sign in form. Click the icon to begin the authentication process. Facebook will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 479c697b933..cea85f073cc 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -2,7 +2,7 @@
Import projects from GitHub and login to your GitLab instance with your GitHub account.
-To enable the GitHub OmniAuth provider you must register your application with GitHub.
+To enable the GitHub OmniAuth provider you must register your application with GitHub.
GitHub will generate an application ID and secret key for you to use.
1. Sign in to GitHub.
@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
- Authorization callback URL is 'http(s)://${YOUR_DOMAIN}'
1. Select "Register application".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
![GitHub app](img/github_app.png)
@@ -49,7 +49,7 @@ GitHub will generate an application ID and secret key for you to use.
For omnibus package:
For GitHub.com:
-
+
```ruby
gitlab_rails['omniauth_providers'] = [
{
@@ -60,9 +60,9 @@ GitHub will generate an application ID and secret key for you to use.
}
]
```
-
+
For GitHub Enterprise:
-
+
```ruby
gitlab_rails['omniauth_providers'] = [
{
@@ -101,10 +101,14 @@ GitHub will generate an application ID and secret key for you to use.
1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
-1. Save the configuration file and run `sudo gitlab-ctl reconfigure`.
+1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
-On the sign in page there should now be a GitHub icon below the regular sign in form.
-Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
+On the sign in page there should now be a GitHub icon below the regular sign in form.
+Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 6d8f3912ede..eec40a9b8f1 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -2,7 +2,7 @@
Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account.
-To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
+To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
GitLab.com will generate an application ID and secret key for you to use.
1. Sign in to GitLab.com
@@ -26,8 +26,8 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Select "Submit".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
- Keep this page open as you continue configuration.
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
![GitLab app](img/gitlab_app.png)
1. On your GitLab server, open the configuration file.
@@ -77,8 +77,12 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
-On the sign in page there should now be a GitLab.com icon below the regular sign in form.
-Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
+On the sign in page there should now be a GitLab.com icon below the regular sign in form.
+Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to your GitLab instance and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 82978b68a34..1e7ad90c5a8 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -74,7 +74,8 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Google icon below the regular sign in form. Click the icon to begin the authentication process. Google will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
@@ -87,3 +88,6 @@ At this point, when users first try to authenticate to your GitLab installation
1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for instructions on how to get here if you closed your window).
1. Scroll down until you find "Product Name". Change the product name to something more descriptive.
1. Add any additional information as you wish - homepage, logo, privacy policy, etc. None of this is required, but it may help your users.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 4a242c321aa..7a809eddac0 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -109,7 +109,8 @@ in your SAML IdP:
1. Change the value of `issuer` to a unique name, which will identify the application
to the IdP.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
in `issuer`.
@@ -314,3 +315,6 @@ For this you need take the following into account:
Make sure that one of the above described scenarios is valid, or the requests will
fail with one of the mentioned errors.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index 696c1011eeb..e0fc1bb801f 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -70,10 +70,9 @@ gitlab_rails['omniauth_providers'] = [
]
```
-1. Save changes and reconfigure gitlab:
-```
-sudo gitlab-ctl reconfigure
-```
+
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a "Sign in with: Shibboleth" icon below the regular sign in form. Click the icon to begin the authentication process. You will be redirected to IdP server (Depends on your Shibboleth module configuration). If everything goes well the user will be returned to GitLab and will be signed in.
@@ -122,4 +121,7 @@ you will not get a shibboleth session!
RequestHeader set X_FORWARDED_PROTO 'https'
RequestHeader set X-Forwarded-Ssl on
-``` \ No newline at end of file
+```
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index abbea09f22f..d0976b6201e 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -74,6 +74,10 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 4b540473a6e..603b826e7f2 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -1,5 +1,20 @@
# Sign-up restrictions
+You can block email addresses of specific domains, or whitelist only some
+specifc domains via the **Application Settings** in the Admin area.
+
+>**Note**: These restrictions are only applied during sign-up. An admin is
+able to add add a user through the admin panel with a disallowed domain. Also
+note that the users can change their email addresses after signup to
+disallowed domains.
+
+## Whitelist email domains
+
+> [Introduced][ce-598] in GitLab 7.11.0
+
+You can restrict users to only signup using email addresses matching the given
+domains list.
+
## Blacklist email domains
> [Introduced][ce-5259] in GitLab 8.10.
@@ -9,13 +24,16 @@ from creating an account on your GitLab server. This is particularly useful to
prevent spam. Disposable email addresses are usually used by malicious users to
create dummy accounts and spam issues.
+## Settings
+
This feature can be activated via the **Application Settings** in the Admin area,
and you have the option of entering the list manually, or uploading a file with
the list.
-The blacklist accepts wildcards, so you can use `*.test.com` to block every
-`test.com` subdomain, or `*.io` to block all domains ending in `.io`. Domains
-should be separated by a whitespace, semicolon, comma, or a new line.
+Both whitelist and blacklist accept wildcards, so for example, you can use
+`*.company.com` to accept every `company.com` subdomain, or `*.io` to block all
+domains ending in `.io`. Domains should be separated by a whitespace,
+semicolon, comma, or a new line.
![Domain Blacklist](img/domain_blacklist.png)
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 008872b59a7..699318e2479 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -237,23 +237,24 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
-| input | references |
-|:-----------------------|:--------------------------- |
-| `@user_name` | specific user |
-| `@group_name` | specific group |
-| `@all` | entire team |
-| `#123` | issue |
-| `!123` | merge request |
-| `$123` | snippet |
-| `~123` | label by ID |
-| `~bug` | one-word label by name |
-| `~"feature request"` | multi-word label by name |
-| `%123` | milestone by ID |
-| `%v1.23` | one-word milestone by name |
-| `%"release candidate"` | multi-word milestone by name |
-| `9ba12248` | specific commit |
-| `9ba12248...b19a04f5` | commit range comparison |
-| `[README](doc/README)` | repository file references |
+| input | references |
+|:---------------------------|:--------------------------------|
+| `@user_name` | specific user |
+| `@group_name` | specific group |
+| `@all` | entire team |
+| `#123` | issue |
+| `!123` | merge request |
+| `$123` | snippet |
+| `~123` | label by ID |
+| `~bug` | one-word label by name |
+| `~"feature request"` | multi-word label by name |
+| `%123` | milestone by ID |
+| `%v1.23` | one-word milestone by name |
+| `%"release candidate"` | multi-word milestone by name |
+| `9ba12248` | specific commit |
+| `9ba12248...b19a04f5` | commit range comparison |
+| `[README](doc/README)` | repository file references |
+| `[README](doc/README#L13)` | repository file line references |
GFM also recognizes certain cross-project references:
diff --git a/features/snippets/user.feature b/features/snippets/user.feature
deleted file mode 100644
index 5b5dadb7b39..00000000000
--- a/features/snippets/user.feature
+++ /dev/null
@@ -1,34 +0,0 @@
-@snippets
-Feature: Snippets User
- Background:
- Given I sign in as a user
- And I have public "Personal snippet one" snippet
- And I have private "Personal snippet private" snippet
- And I have internal "Personal snippet internal" snippet
-
- Scenario: I should see all my snippets
- Given I visit my snippets page
- Then I should see "Personal snippet one" in snippets
- And I should see "Personal snippet private" in snippets
- And I should see "Personal snippet internal" in snippets
-
- Scenario: I can see only my private snippets
- Given I visit my snippets page
- And I click "Private" filter
- Then I should not see "Personal snippet one" in snippets
- And I should not see "Personal snippet internal" in snippets
- And I should see "Personal snippet private" in snippets
-
- Scenario: I can see only my public snippets
- Given I visit my snippets page
- And I click "Public" filter
- Then I should see "Personal snippet one" in snippets
- And I should not see "Personal snippet private" in snippets
- And I should not see "Personal snippet internal" in snippets
-
- Scenario: I can see only my internal snippets
- Given I visit my snippets page
- And I click "Internal" filter
- Then I should see "Personal snippet internal" in snippets
- And I should not see "Personal snippet private" in snippets
- And I should not see "Personal snippet one" in snippets
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
deleted file mode 100644
index 997c605bce2..00000000000
--- a/features/steps/snippets/user.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedSnippet
-
- step 'I visit my snippets page' do
- visit dashboard_snippets_path
- end
-
- step 'I should see "Personal snippet one" in snippets' do
- expect(page).to have_content "Personal snippet one"
- end
-
- step 'I should see "Personal snippet private" in snippets' do
- expect(page).to have_content "Personal snippet private"
- end
-
- step 'I should see "Personal snippet internal" in snippets' do
- expect(page).to have_content "Personal snippet internal"
- end
-
- step 'I should not see "Personal snippet one" in snippets' do
- expect(page).not_to have_content "Personal snippet one"
- end
-
- step 'I should not see "Personal snippet private" in snippets' do
- expect(page).not_to have_content "Personal snippet private"
- end
-
- step 'I should not see "Personal snippet internal" in snippets' do
- expect(page).not_to have_content "Personal snippet internal"
- end
-
- step 'I click "Internal" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Internal"
- end
- end
-
- step 'I click "Private" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Private"
- end
- end
-
- step 'I click "Public" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Public"
- end
- end
-
- def snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
- end
-end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5d7b8e021bb..3a5819d1bab 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -138,6 +138,7 @@ module API
expose :web_url
expose :request_access_enabled
expose :full_name, :full_path
+ expose :parent_id
expose :statistics, if: :statistics do
with_options format_with: -> (value) { value.to_i } do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 7682d286866..5c132bdd6f9 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -73,6 +73,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
post do
@@ -125,7 +126,7 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
- DestroyGroupService.new(group, current_user).execute
+ ::Groups::DestroyService.new(group, current_user).execute
end
desc 'Get a list of projects in this group.' do
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 4d2a8f48267..8beccaaabd1 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -131,7 +131,7 @@ module API
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
- ::Notes::DeleteService.new(user_project, current_user).execute(note)
+ ::Notes::DestroyService.new(user_project, current_user).execute(note)
present note, with: Entities::Note
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 92a70faf1c2..bd4b23195ac 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,7 +16,6 @@ module API
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
- optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
optional :visibility_level, type: Integer, values: [
Gitlab::VisibilityLevel::PRIVATE,
Gitlab::VisibilityLevel::INTERNAL,
@@ -26,16 +25,6 @@ module API
optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
end
-
- def map_public_to_visibility_level(attrs)
- publik = attrs.delete(:public)
- if !publik.nil? && !attrs[:visibility_level].present?
- # Since setting the public attribute to private could mean either
- # private or internal, use the more conservative option, private.
- attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
- end
- attrs
- end
end
resource :projects do
@@ -161,7 +150,7 @@ module API
use :create_params
end
post do
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
@@ -190,7 +179,7 @@ module API
user = User.find_by(id: params.delete(:user_id))
not_found!('User') unless user
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
@@ -268,14 +257,14 @@ module API
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
:shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :public, :visibility_level, :public_builds,
+ :lfs_enabled, :visibility_level, :public_builds,
:request_access_enabled, :only_allow_merge_if_build_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
put ':id' do
authorize_admin_project
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 0ed468626b7..4980a90f952 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -293,7 +293,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- DeleteUserService.new(current_user).execute(user)
+ ::Users::DestroyService.new(current_user).execute(user)
end
desc 'Block a user. Available only for admins.'
diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serializer/ci/variables.rb
index 3a9443bfcd9..c059c454eac 100644
--- a/lib/gitlab/serialize/ci/variables.rb
+++ b/lib/gitlab/serializer/ci/variables.rb
@@ -1,5 +1,5 @@
module Gitlab
- module Serialize
+ module Serializer
module Ci
# This serializer could make sure our YAML variables' keys and values
# are always strings. This is more for legacy build data because
diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb
new file mode 100644
index 00000000000..bf2c0acc729
--- /dev/null
+++ b/lib/gitlab/serializer/pagination.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Serializer
+ class Pagination
+ class InvalidResourceError < StandardError; end
+ include ::API::Helpers::Pagination
+
+ def initialize(request, response)
+ @request = request
+ @response = response
+ end
+
+ def paginate(resource)
+ if resource.respond_to?(:page)
+ super(resource)
+ else
+ raise InvalidResourceError
+ end
+ end
+
+ private
+
+ # Methods needed by `API::Helpers::Pagination`
+ #
+
+ attr_reader :request
+
+ def params
+ @request.query_parameters
+ end
+
+ def header(header, value)
+ @response.headers[header] = value
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 9581d966237..249c69f586a 100644
--- a/package.json
+++ b/package.json
@@ -11,27 +11,27 @@
"webpack-prod": "NODE_ENV=production npm run webpack"
},
"dependencies": {
- "babel": "^5.8.38",
- "babel-core": "^5.8.38",
- "babel-loader": "^5.4.2",
+ "babel-core": "^6.22.1",
+ "babel-loader": "^6.2.10",
+ "babel-preset-es2015": "^6.22.0",
+ "babel-preset-stage-2": "^6.22.0",
"bootstrap-sass": "3.3.6",
"compression-webpack-plugin": "^0.3.2",
"d3": "3.5.11",
"dropzone": "4.2.0",
- "exports-loader": "^0.6.3",
"imports-loader": "^0.6.5",
"jquery": "2.2.1",
"jquery-ui": "github:jquery/jquery-ui#1.11.4",
"jquery-ujs": "1.2.1",
- "json-loader": "^0.5.4",
"mousetrap": "1.4.6",
+ "pikaday": "^1.5.1",
"select2": "3.5.2-browserify",
- "stats-webpack-plugin": "^0.4.2",
+ "stats-webpack-plugin": "^0.4.3",
"underscore": "1.8.3",
"vue": "2.0.3",
"vue-resource": "0.9.3",
- "webpack": "^1.14.0",
- "webpack-dev-server": "^1.16.2"
+ "webpack": "^2.2.1",
+ "webpack-dev-server": "^2.3.0"
},
"devDependencies": {
"eslint": "^3.10.1",
@@ -42,10 +42,10 @@
"istanbul": "^0.4.5",
"jasmine-core": "^2.5.2",
"jasmine-jquery": "^2.1.1",
- "karma": "^1.3.0",
+ "karma": "^1.4.1",
"karma-jasmine": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^1.8.0"
+ "karma-webpack": "^2.0.2"
}
}
diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb
index 4a63c760744..c199f6acab2 100644
--- a/rubocop/cop/gem_fetcher.rb
+++ b/rubocop/cop/gem_fetcher.rb
@@ -1,17 +1,15 @@
module RuboCop
module Cop
- # Cop that checks for all gems specified in the Gemfile, and will
- # alert if any gem is to be fetched not from the RubyGems index.
- # This enforcement is done so as to minimize external build
- # dependencies and build times.
+ # This cop prevents usage of the `git` and `github` arguments to `gem` in a
+ # `Gemfile` in order to avoid additional points of failure beyond
+ # rubygems.org.
class GemFetcher < RuboCop::Cop::Cop
MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
GIT_KEYS = [:git, :github]
def on_send(node)
- file_path = node.location.expression.source_buffer.name
- return unless file_path.end_with?("Gemfile")
+ return unless gemfile?(node)
func_name = node.children[1]
return unless func_name == :gem
@@ -19,10 +17,21 @@ module RuboCop
node.children.last.each_node(:pair) do |pair|
key_name = pair.children[0].children[0].to_sym
if GIT_KEYS.include?(key_name)
- add_offense(node, :selector)
+ add_offense(node, pair.source_range, MSG)
end
end
end
+
+ private
+
+ def gemfile?(node)
+ node
+ .location
+ .expression
+ .source_buffer
+ .name
+ .end_with?("Gemfile")
+ end
end
end
end
diff --git a/rubocop/cop/migration/column_with_default.rb b/rubocop/cop/migration/add_column.rb
index 97ee8b11044..1490fcdd814 100644
--- a/rubocop/cop/migration/column_with_default.rb
+++ b/rubocop/cop/migration/add_column.rb
@@ -1,15 +1,17 @@
+require_relative '../../migration_helpers'
+
module RuboCop
module Cop
module Migration
# Cop that checks if columns are added in a way that doesn't require
# downtime.
- class ColumnWithDefault < RuboCop::Cop::Cop
+ class AddColumn < RuboCop::Cop::Cop
include MigrationHelpers
WHITELISTED_TABLES = [:application_settings]
- MSG = 'add_column with a default value requires downtime, ' \
- 'use add_column_with_default instead'
+ MSG = '`add_column` with a default value requires downtime, ' \
+ 'use `add_column_with_default` instead'
def on_send(node)
return unless in_migration?(node)
diff --git a/rubocop/cop/migration/add_column_with_default.rb b/rubocop/cop/migration/add_column_with_default.rb
new file mode 100644
index 00000000000..747d7caf1ef
--- /dev/null
+++ b/rubocop/cop/migration/add_column_with_default.rb
@@ -0,0 +1,34 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if `add_column_with_default` is used with `up`/`down` methods
+ # and not `change`.
+ class AddColumnWithDefault < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_column_with_default` is not reversible so you must manually define ' \
+ 'the `up` and `down` methods in your migration class, using `remove_column` in `down`'
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_column_with_default
+
+ node.each_ancestor(:def) do |def_node|
+ next unless method_name(def_node) == :change
+
+ add_offense(def_node, :name)
+ end
+ end
+
+ def method_name(node)
+ node.children.first
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb
index d9247a1f7ea..5e6766f6994 100644
--- a/rubocop/cop/migration/add_index.rb
+++ b/rubocop/cop/migration/add_index.rb
@@ -1,3 +1,5 @@
+require_relative '../../migration_helpers'
+
module RuboCop
module Cop
module Migration
@@ -5,7 +7,7 @@ module RuboCop
class AddIndex < RuboCop::Cop::Cop
include MigrationHelpers
- MSG = 'add_index requires downtime, use add_concurrent_index instead'
+ MSG = '`add_index` requires downtime, use `add_concurrent_index` instead'
def on_def(node)
return unless in_migration?(node)
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 7f20754ee51..aa35fb1701c 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,4 +1,4 @@
-require_relative 'migration_helpers'
-require_relative 'cop/migration/add_index'
-require_relative 'cop/migration/column_with_default'
require_relative 'cop/gem_fetcher'
+require_relative 'cop/migration/add_column'
+require_relative 'cop/migration/add_column_with_default'
+require_relative 'cop/migration/add_index'
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 91d6f39a5bf..275561502cd 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -24,6 +24,10 @@ FactoryGirl.define do
target factory: :merge_request
end
+ trait :marked do
+ action { Todo::MARKED }
+ end
+
trait :approval_required do
action { Todo::APPROVAL_REQUIRED }
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 34f47daf0e5..7225f38b7e5 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
+ include DragTo
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
@@ -188,7 +189,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'moves issue to done' do
- drag_to(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 0, list_to_index: 2)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -201,7 +202,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes all of the same issue to done' do
- drag_to(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 0, list_to_index: 2)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -215,7 +216,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'lists' do
it 'changes position of list' do
- drag_to(list_from_index: 1, list_to_index: 0, selector: '.board-header')
+ drag(list_from_index: 1, list_to_index: 0, selector: '.board-header')
wait_for_board_cards(1, 2)
wait_for_board_cards(2, 8)
@@ -226,7 +227,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves between lists' do
- drag_to(list_from_index: 0, card_index: 1, list_to_index: 1)
+ drag(list_from_index: 0, from_index: 1, list_to_index: 1)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -237,7 +238,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves between lists' do
- drag_to(list_from_index: 1, list_to_index: 0)
+ drag(list_from_index: 1, list_to_index: 0)
wait_for_board_cards(1, 9)
wait_for_board_cards(2, 1)
@@ -248,7 +249,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves from done' do
- drag_to(list_from_index: 2, list_to_index: 1)
+ drag(list_from_index: 2, list_to_index: 1)
expect(find('.board:nth-child(2)')).to have_content(issue8.title)
@@ -615,14 +616,13 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- def drag_to(list_from_index: 0, card_index: 0, to_index: 0, list_to_index: 0, selector: '.board-list')
- evaluate_script("simulateDrag({scrollable: document.getElementById('board-app'), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{card_index}}, to: {el: $('.board-list').eq(#{list_to_index}).get(0), index: #{to_index}}});")
-
- Timeout.timeout(Capybara.default_max_wait_time) do
- loop until page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').zero?
- end
-
- wait_for_vue_resource
+ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ drag_to(selector: selector,
+ scrollable: '#board-app',
+ list_from_index: list_from_index,
+ from_index: from_index,
+ to_index: to_index,
+ list_to_index: list_to_index)
end
def wait_for_board_cards(board_number, expected_cards)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 9cc50167395..bad6b56a18a 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -241,7 +241,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.due_date') do
click_link 'Edit'
- click_link Date.today.day
+ click_button Date.today.day
wait_for_vue_resource
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index f3a5b565122..fec86128d03 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -251,6 +251,8 @@ describe 'Copy as GFM', feature: true, js: true do
'SanitizationFilter',
<<-GFM.strip_heredoc
+ <a name="named-anchor"></a>
+
<sub>sub</sub>
<dl>
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 8b3e2fa93a2..8c64b050e19 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -72,8 +72,8 @@ feature 'Expand and collapse diffs', js: true, feature: true do
it 'collapses large diffs for renamed files by default' do
expect(large_diff_renamed).not_to have_selector('.code')
expect(large_diff_renamed).to have_selector('.nothing-here-block')
- expect(large_diff_renamed).to have_selector('.file-title .deletion')
- expect(large_diff_renamed).to have_selector('.file-title .addition')
+ expect(large_diff_renamed).to have_selector('.js-file-title .deletion')
+ expect(large_diff_renamed).to have_selector('.js-file-title .addition')
end
it 'shows non-renderable diffs as such immediately, regardless of their size' do
@@ -115,9 +115,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding a large diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `large_diff.md` title
- all('.file-title')[1].click
+ all('.diff-toggle-caret')[1].click
wait_for_ajax
end
@@ -159,9 +159,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding the diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `large_diff.md` title
- all('.file-title')[1].click
+ all('.diff-toggle-caret')[1].click
wait_for_ajax
end
@@ -181,9 +181,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'collapsing an expanded diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'hides the diff content' do
@@ -194,9 +194,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 're-expanding the same diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'shows the diff content' do
@@ -290,9 +290,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'collapsing an expanded diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'hides the diff content' do
@@ -303,9 +303,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 're-expanding the same diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'shows the diff content' do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index a515c92db37..37b7c20239f 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -45,6 +45,23 @@ feature 'Group', feature: true do
end
end
+ describe 'create a nested group' do
+ let(:group) { create(:group, path: 'foo') }
+
+ before do
+ visit subgroups_group_path(group)
+ click_link 'New Subgroup'
+ end
+
+ it 'creates a nested group' do
+ fill_in 'Group path', with: 'bar'
+ click_button 'Create group'
+
+ expect(current_path).to eq(group_path('foo/bar'))
+ expect(page).to have_content("Group 'bar' was successfully created.")
+ end
+ end
+
describe 'group edit' do
let(:group) { create(:group) }
let(:path) { edit_group_path(group) }
@@ -117,7 +134,7 @@ feature 'Group', feature: true do
visit path
click_link 'Subgroups'
- expect(page).to have_content(nested_group.full_name)
+ expect(page).to have_content(nested_group.name)
end
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 3f70a6aa75f..6f7046c8461 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -801,4 +801,26 @@ describe 'Filter issues', js: true, feature: true do
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
end
+
+ context 'URL has a trailing slash' do
+ before do
+ visit "#{namespace_project_issues_path(project.namespace, project)}/"
+ end
+
+ it 'milestone dropdown loads milestones' do
+ input_filtered_search("milestone:", submit: false)
+
+ within('#js-dropdown-milestone') do
+ expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 2)
+ end
+ end
+
+ it 'label dropdown load labels' do
+ input_filtered_search("label:", submit: false)
+
+ within('#js-dropdown-label') do
+ expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
+ end
+ end
+ end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 394eb31aff8..755162a1eb5 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -78,8 +78,8 @@ describe 'Issues', feature: true do
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
- page.within '.ui-datepicker' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
expect(find('#issuable-due-date').value).to eq date.to_s
@@ -110,8 +110,8 @@ describe 'Issues', feature: true do
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
- page.within '.ui-datepicker' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
expect(find('#issuable-due-date').value).to eq date.to_s
@@ -624,8 +624,8 @@ describe 'Issues', feature: true do
page.within '.due_date' do
click_link 'Edit'
- page.within '.ui-datepicker-calendar' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
wait_for_ajax
@@ -635,11 +635,13 @@ describe 'Issues', feature: true do
end
it 'removes due date from issue' do
+ date = Date.today.at_beginning_of_month + 2.days
+
page.within '.due_date' do
click_link 'Edit'
- page.within '.ui-datepicker-calendar' do
- first('.ui-state-default').click
+ page.within '.pika-single' do
+ click_button date.day
end
wait_for_ajax
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index fb3a1ae4bd0..957e913bf95 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -29,8 +29,6 @@ describe 'Merge request', :feature, :js do
it 'shows widget status after creating new merge request' do
click_button 'Submit merge request'
- expect(find('.mr-state-widget')).to have_content('Checking ability to merge automatically')
-
wait_for_ajax
expect(page).to have_selector('.accept_merge_request')
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
index aadd72a9f8e..8de9942c54e 100644
--- a/spec/features/milestones/milestones_spec.rb
+++ b/spec/features/milestones/milestones_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Milestone draggable', feature: true, js: true do
include WaitForAjax
+ include DragTo
let(:milestone) { create(:milestone, project: project, title: 8.14) }
let(:project) { create(:empty_project, :public) }
@@ -75,7 +76,7 @@ describe 'Milestone draggable', feature: true, js: true do
create(:issue, params.merge(title: 'Foo', project: project, milestone: milestone))
visit namespace_project_milestone_path(project.namespace, project, milestone)
- issue.drag_to(issue_target)
+ drag_to(selector: '.issues-sortable-list', list_to_index: 1)
wait_for_ajax
end
@@ -85,7 +86,7 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
page.find("a[href='#tab-merge-requests']").click
- merge_request.drag_to(merge_request_target)
+ drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
wait_for_ajax
end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 55a01057c83..eb7b8a24669 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -34,7 +34,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
# Set date to 1st of next month
find_field("Expires at").trigger('focus')
- find("a[title='Next']").click
+ find(".pika-next").click
click_on "1"
# Scopes
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index fc88fd74af8..582349d8d5b 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -22,7 +22,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
expect(page).to have_selector('.blob-content-holder')
- page.within('.file-title') do
+ page.within('.js-file-title') do
expect(page).to have_content('CHANGELOG')
end
end
@@ -35,7 +35,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
expect(page).to have_selector('.blob-content-holder')
- page.within('.file-title') do
+ page.within('.js-file-title') do
expect(page).to have_content('application.js')
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 97ce9cdfd87..1e900d7e660 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
feature 'Prioritize labels', feature: true do
include WaitForAjax
+ include DragTo
let(:user) { create(:user) }
let(:group) { create(:group) }
@@ -99,7 +100,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content 'wontfix'
# Sort labels
- find("#project_label_#{bug.id}").drag_to find("#group_label_#{feature.id}")
+ drag_to(selector: '.js-prioritized-labels', from_index: 1, to_index: 2)
page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature')
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 f136d9ce0fa..c3f45be6e4b 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
@@ -14,15 +14,16 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
login_as(master)
end
- scenario 'expiration date is displayed in the members list', js: true do
+ scenario 'expiration date is displayed in the members list' do
travel_to Time.zone.parse('2016-08-06 08:00') do
- visit namespace_project_settings_members_path(project.namespace, project)
+ date = 4.days.from_now
+ visit namespace_project_project_members_path(project.namespace, project)
+
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
- fill_in 'expires_at', with: '2016-08-10'
+ fill_in 'expires_at', with: date.to_s(:medium)
+ click_on 'Add to project'
end
- find('.users-project-form').click
- click_on 'Add to project'
page.within "#project_member_#{new_member.project_members.first.id}" do
expect(page).to have_content('Expires in 4 days')
@@ -32,11 +33,12 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
scenario 'change expiration date' do
travel_to Time.zone.parse('2016-08-06 08:00') do
- project.team.add_users([new_member.id], :developer, expires_at: '2016-09-06')
+ date = 3.days.from_now
+ project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium))
visit namespace_project_project_members_path(project.namespace, project)
page.within "#project_member_#{new_member.project_members.first.id}" do
- find('.js-access-expiration-date').set '2016-08-09'
+ find('.js-access-expiration-date').set date.to_s(:medium)
wait_for_ajax
expect(page).to have_content('Expires in 3 days')
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index ca18ac073d8..6555b2fc6c1 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -35,6 +35,10 @@ describe 'Pipelines', :feature, :js do
it 'contains pipeline commit short SHA' do
expect(page).to have_content(pipeline.short_sha)
end
+
+ it 'contains branch name' do
+ expect(page).to have_content(pipeline.ref)
+ end
end
end
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb
index 395c61a4743..e4aca25a339 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/features/protected_branches/access_control_ce_spec.rb
@@ -26,7 +26,11 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-push").click
- within('.js-allowed-to-push-container') { click_on access_type_name }
+
+ within('.js-allowed-to-push-container') do
+ expect(first("li")).to have_content("Roles")
+ click_on access_type_name
+ end
end
wait_for_ajax
@@ -61,7 +65,11 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-merge").click
- within('.js-allowed-to-merge-container') { click_on access_type_name }
+
+ within('.js-allowed-to-merge-container') do
+ expect(first("li")).to have_content("Roles")
+ click_on access_type_name
+ end
end
wait_for_ajax
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
new file mode 100644
index 00000000000..191c2fb9a22
--- /dev/null
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'User Snippets', feature: true do
+ let(:author) { create(:user) }
+ let!(:public_snippet) { create(:personal_snippet, :public, author: author, title: "This is a public snippet") }
+ let!(:internal_snippet) { create(:personal_snippet, :internal, author: author, title: "This is an internal snippet") }
+ let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") }
+
+ background do
+ login_as author
+ visit dashboard_snippets_path
+ end
+
+ scenario 'View all of my snippets' do
+ expect(page).to have_content(public_snippet.title)
+ expect(page).to have_content(internal_snippet.title)
+ expect(page).to have_content(private_snippet.title)
+ end
+
+ scenario 'View my public snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Public"
+ end
+
+ expect(page).to have_content(public_snippet.title)
+ expect(page).not_to have_content(internal_snippet.title)
+ expect(page).not_to have_content(private_snippet.title)
+ end
+
+ scenario 'View my internal snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Internal"
+ end
+
+ expect(page).not_to have_content(public_snippet.title)
+ expect(page).to have_content(internal_snippet.title)
+ expect(page).not_to have_content(private_snippet.title)
+ end
+
+ scenario 'View my private snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Private"
+ end
+
+ expect(page).not_to have_content(public_snippet.title)
+ expect(page).not_to have_content(internal_snippet.title)
+ expect(page).to have_content(private_snippet.title)
+ end
+end
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index d1f2bc78884..e8f06916d53 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -98,15 +98,58 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
- it 'filters by action' do
- click_button 'Action'
- within '.dropdown-menu-action' do
- click_link 'Assigned'
+ describe 'filter by action' do
+ before do
+ create(:todo, :build_failed, user: user_1, author: user_2, project: project_1)
+ create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue)
end
- wait_for_ajax
+ it 'filters by Assigned' do
+ filter_action('Assigned')
+
+ expect_to_see_action(:assigned)
+ end
+
+ it 'filters by Mentioned' do
+ filter_action('Mentioned')
+
+ expect_to_see_action(:mentioned)
+ end
+
+ it 'filters by Added' do
+ filter_action('Added')
+
+ expect_to_see_action(:marked)
+ end
+
+ it 'filters by Pipelines' do
+ filter_action('Pipelines')
- expect(find('.todos-list')).to have_content ' assigned you '
- expect(find('.todos-list')).not_to have_content ' mentioned '
+ expect_to_see_action(:build_failed)
+ end
+
+ def filter_action(name)
+ click_button 'Action'
+ within '.dropdown-menu-action' do
+ click_link name
+ end
+
+ wait_for_ajax
+ end
+
+ def expect_to_see_action(action_name)
+ action_names = {
+ assigned: ' assigned you ',
+ mentioned: ' mentioned ',
+ marked: ' added a todo for ',
+ build_failed: ' build failed for '
+ }
+
+ action_name_text = action_names.delete(action_name)
+ expect(find('.todos-list')).to have_content action_name_text
+ action_names.each_value do |other_action_text|
+ expect(find('.todos-list')).not_to have_content other_action_text
+ end
+ end
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 8d268dbcf36..25f23826648 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -79,4 +79,86 @@ describe MergeRequestsHelper do
expect(mr_widget_refresh_url(nil)).to eq('')
end
end
+
+ describe '#mr_closes_issues' do
+ let(:user_1) { create(:user) }
+ let(:user_2) { create(:user) }
+
+ let(:project_1) { create(:project, :private, creator: user_1, namespace: user_1.namespace) }
+ let(:project_2) { create(:project, :private, creator: user_2, namespace: user_2.namespace) }
+
+ let(:issue_1) { create(:issue, project: project_1) }
+ let(:issue_2) { create(:issue, project: project_2) }
+
+ let(:merge_request) { create(:merge_request, source_project: project_1, target_project: project_1,) }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project_1, target_project: project_1,
+ description: "Fixes #{issue_1.to_reference} Fixes #{issue_2.to_reference(project_1)}")
+ end
+
+ before do
+ project_1.team << [user_2, :developer]
+ project_2.team << [user_2, :developer]
+ allow(merge_request.project).to receive(:default_branch).and_return(merge_request.target_branch)
+ @merge_request = merge_request
+ end
+
+ context 'user without access to another private project' do
+ let(:current_user) { user_1 }
+
+ it 'cannot see that project\'s issue that will be closed on acceptance' do
+ expect(mr_closes_issues).to contain_exactly(issue_1)
+ end
+ end
+
+ context 'user with access to another private project' do
+ let(:current_user) { user_2 }
+
+ it 'can see that project\'s issue that will be closed on acceptance' do
+ expect(mr_closes_issues).to contain_exactly(issue_1, issue_2)
+ end
+ end
+ end
+
+ describe '#mr_issues_mentioned_but_not_closing' do
+ let(:user_1) { create(:user) }
+ let(:user_2) { create(:user) }
+
+ let(:project_1) { create(:project, :private, creator: user_1, namespace: user_1.namespace) }
+ let(:project_2) { create(:project, :private, creator: user_2, namespace: user_2.namespace) }
+
+ let(:issue_1) { create(:issue, project: project_1) }
+ let(:issue_2) { create(:issue, project: project_2) }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project_1, target_project: project_1,
+ description: "#{issue_1.to_reference} #{issue_2.to_reference(project_1)}")
+ end
+
+ before do
+ project_1.team << [user_2, :developer]
+ project_2.team << [user_2, :developer]
+ allow(merge_request.project).to receive(:default_branch).and_return(merge_request.target_branch)
+ @merge_request = merge_request
+ end
+
+ context 'user without access to another private project' do
+ let(:current_user) { user_1 }
+
+ it 'cannot see that project\'s issue that will be closed on acceptance' do
+ expect(mr_issues_mentioned_but_not_closing).to contain_exactly(issue_1)
+ end
+ end
+
+ context 'user with access to another private project' do
+ let(:current_user) { user_2 }
+
+ it 'can see that project\'s issue that will be closed on acceptance' do
+ expect(mr_issues_mentioned_but_not_closing).to contain_exactly(issue_1, issue_2)
+ end
+ end
+ end
end
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
index 559723bafcc..789f5dc9f49 100644
--- a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
+++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
@@ -7,6 +7,9 @@ describe('Store', () => {
store = new gl.commits.pipelines.PipelinesStore();
});
+ // unregister intervals and event handlers
+ afterEach(() => gl.VueRealtimeListener.reset());
+
it('should start with a blank state', () => {
expect(store.state.pipelines.length).toBe(0);
});
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
index f4b0d60db34..fa9d03c8a9a 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
@@ -9,7 +9,7 @@ require('~/filtered_search/dropdown_user');
let dropdownUser;
beforeEach(() => {
- spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
@@ -39,7 +39,7 @@ require('~/filtered_search/dropdown_user');
describe('config droplabAjaxFilter\'s endpoint', () => {
beforeEach(() => {
- spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
});
diff --git a/spec/lib/gitlab/serialize/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 7ea74da5252..b810c68ea03 100644
--- a/spec/lib/gitlab/serialize/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Serialize::Ci::Variables do
+describe Gitlab::Serializer::Ci::Variables do
subject do
described_class.load(described_class.dump(object))
end
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
new file mode 100644
index 00000000000..519eb1b274f
--- /dev/null
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::Serializer::Pagination do
+ let(:request) { spy('request') }
+ let(:response) { spy('response') }
+ let(:headers) { spy('headers') }
+
+ before do
+ allow(request).to receive(:query_parameters)
+ .and_return(params)
+
+ allow(response).to receive(:headers)
+ .and_return(headers)
+ end
+
+ let(:pagination) { described_class.new(request, response) }
+
+ describe '#paginate' do
+ subject { pagination.paginate(resource) }
+
+ let(:resource) { User.all }
+ let(:params) { { page: 1, per_page: 2 } }
+
+ context 'when a multiple resources are present in relation' do
+ before { create_list(:user, 3) }
+
+ it 'correctly paginates the resource' do
+ expect(subject.count).to be 2
+ end
+
+ it 'appends relevant headers' do
+ expect(headers).to receive(:[]=).with('X-Total', '3')
+ expect(headers).to receive(:[]=).with('X-Total-Pages', '2')
+ expect(headers).to receive(:[]=).with('X-Per-Page', '2')
+
+ subject
+ end
+ end
+
+ context 'when an invalid resource is about to be paginated' do
+ let(:resource) { create(:user) }
+
+ it 'raises error' do
+ expect { subject }.to raise_error(
+ described_class::InvalidResourceError)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 30443534cca..e008ec28fa4 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -14,12 +14,14 @@ describe Group, 'Routable' do
describe 'Callbacks' do
it 'creates route record on create' do
expect(group.route.path).to eq(group.path)
+ expect(group.route.name).to eq(group.name)
end
it 'updates route record on path change' do
- group.update_attributes(path: 'wow')
+ group.update_attributes(path: 'wow', name: 'much')
expect(group.route.path).to eq('wow')
+ expect(group.route.name).to eq('much')
end
it 'ensure route path uniqueness across different objects' do
@@ -78,4 +80,34 @@ describe Group, 'Routable' do
it { is_expected.to eq([nested_group]) }
end
+
+ describe '#full_path' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ it { expect(group.full_path).to eq(group.path) }
+ it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
+ end
+
+ describe '#full_name' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ it { expect(group.full_name).to eq(group.name) }
+ it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
+ end
+end
+
+describe Project, 'Routable' do
+ describe '#full_path' do
+ let(:project) { build_stubbed(:empty_project) }
+
+ it { expect(project.full_path).to eq "#{project.namespace.path}/#{project.path}" }
+ end
+
+ describe '#full_name' do
+ let(:project) { build_stubbed(:empty_project) }
+
+ it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index e1e99300489..a01741a9971 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -97,7 +97,7 @@ describe MergeRequest, models: true do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues! }.to change(subject.merge_requests_closing_issues, :count).by(1)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
end
it 'does not cache issues from external trackers' do
@@ -106,7 +106,7 @@ describe MergeRequest, models: true do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues! }.not_to change(subject.merge_requests_closing_issues, :count)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end
end
@@ -300,7 +300,7 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
- expect(subject.issues_mentioned_but_not_closing).to match_array([mentioned_issue])
+ expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 7bb1657bc3a..35d932f1c64 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -3,21 +3,32 @@ require 'spec_helper'
describe Namespace, models: true do
let!(:namespace) { create(:namespace) }
- it { is_expected.to have_many :projects }
- it { is_expected.to have_many :project_statistics }
- it { is_expected.to belong_to :parent }
- it { is_expected.to have_many :children }
+ describe 'associations' do
+ it { is_expected.to have_many :projects }
+ it { is_expected.to have_many :project_statistics }
+ it { is_expected.to belong_to :parent }
+ it { is_expected.to have_many :children }
+ end
- it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
- it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(255) }
+ it { is_expected.to validate_presence_of(:path) }
+ it { is_expected.to validate_length_of(:path).is_at_most(255) }
+ it { is_expected.to validate_presence_of(:owner) }
- it { is_expected.to validate_length_of(:description).is_at_most(255) }
+ it 'does not allow too deep nesting' do
+ ancestors = (1..21).to_a
+ nested = build(:namespace, parent: namespace)
- it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_length_of(:path).is_at_most(255) }
+ allow(nested).to receive(:ancestors).and_return(ancestors)
- it { is_expected.to validate_presence_of(:owner) }
+ expect(nested).not_to be_valid
+ expect(nested.errors[:parent_id].first).to eq('has too deep level of nesting')
+ end
+ end
describe "Respond to" do
it { is_expected.to respond_to(:human_name) }
@@ -175,22 +186,6 @@ describe Namespace, models: true do
end
end
- describe '#full_path' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
- it { expect(group.full_path).to eq(group.path) }
- it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
- end
-
- describe '#full_name' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
- it { expect(group.full_name).to eq(group.name) }
- it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
- end
-
describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f6b159d76e..4bca0229e7a 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -135,7 +135,7 @@ describe JiraService, models: true do
url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{merge_request.diff_head_sha}",
title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
- status: { resolved: true, icon: { url16x16: "http://www.openwebgraphics.com/resources/data/1768/16x16_apply.png", title: "Closed" } }
+ status: { resolved: true }
}
)
).once
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2129bcbd74d..35f3dd00870 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -275,13 +275,6 @@ describe Project, models: true do
it { is_expected.to delegate_method(:add_master).to(:team) }
end
- describe '#name_with_namespace' do
- let(:project) { build_stubbed(:empty_project) }
-
- it { expect(project.name_with_namespace).to eq "#{project.namespace.human_name} / #{project.name}" }
- it { expect(project.human_name).to eq project.name_with_namespace }
- end
-
describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
@@ -1840,6 +1833,20 @@ describe Project, models: true do
end
end
+ describe '#parent' do
+ let(:project) { create(:empty_project) }
+
+ it { expect(project.parent).to eq(project.namespace) }
+ end
+
+ describe '#parent_changed?' do
+ let(:project) { create(:empty_project) }
+
+ before { project.namespace_id = 7 }
+
+ it { expect(project.parent_changed?).to be_truthy }
+ end
+
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index dd2a5109abc..0b222022e62 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Route, models: true do
- let!(:group) { create(:group, path: 'gitlab') }
+ let!(:group) { create(:group, path: 'gitlab', name: 'gitlab') }
let!(:route) { group.route }
describe 'relationships' do
@@ -15,17 +15,42 @@ describe Route, models: true do
end
describe '#rename_descendants' do
- let!(:nested_group) { create(:group, path: "test", parent: group) }
- let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
- let!(:similar_group) { create(:group, path: 'gitlab-org') }
+ let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
+ let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
+ let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') }
- before { route.update_attributes(path: 'bar') }
+ context 'path update' do
+ context 'when route name is set' do
+ before { route.update_attributes(path: 'bar') }
- it "updates children routes with new path" do
- expect(described_class.exists?(path: 'bar')).to be_truthy
- expect(described_class.exists?(path: 'bar/test')).to be_truthy
- expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
- expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
+ it "updates children routes with new path" do
+ expect(described_class.exists?(path: 'bar')).to be_truthy
+ expect(described_class.exists?(path: 'bar/test')).to be_truthy
+ expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
+ expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
+ end
+ end
+
+ context 'when route name is nil' do
+ before do
+ route.update_column(:name, nil)
+ end
+
+ it "does not fail" do
+ expect(route.update_attributes(path: 'bar')).to be_truthy
+ end
+ end
+ end
+
+ context 'name update' do
+ before { route.update_attributes(name: 'bar') }
+
+ it "updates children routes with new path" do
+ expect(described_class.exists?(name: 'bar')).to be_truthy
+ expect(described_class.exists?(name: 'bar / test')).to be_truthy
+ expect(described_class.exists?(name: 'bar / test / foo')).to be_truthy
+ expect(described_class.exists?(name: 'gitlab-org')).to be_truthy
+ end
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fe88ec63af6..7fd49c73b37 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -141,6 +141,11 @@ describe User, models: true do
user = build(:user, email: "example@test.com")
expect(user).to be_invalid
end
+
+ it 'accepts example@test.com when added by another user' do
+ user = build(:user, email: "example@test.com", created_by_id: 1)
+ expect(user).to be_valid
+ end
end
context 'domain blacklist' do
@@ -159,6 +164,11 @@ describe User, models: true do
user = build(:user, email: 'info@example.com')
expect(user).not_to be_valid
end
+
+ it 'accepts info@example.com when added by another user' do
+ user = build(:user, email: 'info@example.com', created_by_id: 1)
+ expect(user).to be_valid
+ end
end
context 'when a signup domain is blacklisted but a wildcard subdomain is allowed' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index a027c23bb88..15592f1f702 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -179,6 +179,7 @@ describe API::Groups, api: true do
expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
expect(json_response['full_name']).to eq(group1.full_name)
expect(json_response['full_path']).to eq(group1.full_path)
+ expect(json_response['parent_id']).to eq(group1.parent_id)
expect(json_response['projects']).to be_an Array
expect(json_response['projects'].length).to eq(2)
expect(json_response['shared_projects']).to be_an Array
@@ -398,6 +399,19 @@ describe API::Groups, api: true do
expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
end
+ it "creates a nested group" do
+ parent = create(:group)
+ parent.add_owner(user3)
+ group = attributes_for(:group, { parent_id: parent.id })
+
+ post api("/groups", user3), group
+
+ expect(response).to have_http_status(201)
+
+ expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}")
+ expect(json_response["parent_id"]).to eq(parent.id)
+ end
+
it "does not create group, duplicate" do
post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 225e2e005df..ac0bbec44e0 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -359,13 +359,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'sets a project as public using :public' do
- project = attributes_for(:project, { public: true })
- post api('/projects', user), project
- expect(json_response['public']).to be_truthy
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
- end
-
it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api('/projects', user), project
@@ -373,13 +366,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'sets a project as internal overriding :public' do
- project = attributes_for(:project, :internal, { public: true })
- post api('/projects', user), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
- end
-
it 'sets a project as private' do
project = attributes_for(:project, :private)
post api('/projects', user), project
@@ -387,13 +373,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'sets a project as private using :public' do
- project = attributes_for(:project, { public: false })
- post api('/projects', user), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
- end
-
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api('/projects', user), project
@@ -431,13 +410,14 @@ describe API::Projects, api: true do
end
context 'when a visibility level is restricted' do
+ let(:project_param) { attributes_for(:project, :public) }
+
before do
- @project = attributes_for(:project, { public: true })
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'does not allow a non-admin to use a restricted visibility level' do
- post api('/projects', user), @project
+ post api('/projects', user), project_param
expect(response).to have_http_status(400)
expect(json_response['message']['visibility_level'].first).to(
@@ -446,7 +426,8 @@ describe API::Projects, api: true do
end
it 'allows an admin to override restricted visibility settings' do
- post api('/projects', admin), @project
+ post api('/projects', admin), project_param
+
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to(
eq(Gitlab::VisibilityLevel::PUBLIC)
@@ -499,15 +480,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'sets a project as public using :public' do
- project = attributes_for(:project, { public: true })
- post api("/projects/user/#{user.id}", admin), project
-
- expect(response).to have_http_status(201)
- expect(json_response['public']).to be_truthy
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
- end
-
it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api("/projects/user/#{user.id}", admin), project
@@ -517,14 +489,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'sets a project as internal overriding :public' do
- project = attributes_for(:project, :internal, { public: true })
- post api("/projects/user/#{user.id}", admin), project
- expect(response).to have_http_status(201)
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
- end
-
it 'sets a project as private' do
project = attributes_for(:project, :private)
post api("/projects/user/#{user.id}", admin), project
@@ -532,13 +496,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'sets a project as private using :public' do
- project = attributes_for(:project, { public: false })
- post api("/projects/user/#{user.id}", admin), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
- end
-
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api("/projects/user/#{user.id}", admin), project
@@ -865,7 +822,7 @@ describe API::Projects, api: true do
it 'creates a new project snippet' do
post api("/projects/#{project.id}/snippets", user),
title: 'api test', file_name: 'sample.rb', code: 'test',
- visibility_level: '0'
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('api test')
end
@@ -1114,7 +1071,7 @@ describe API::Projects, api: true do
end
it 'updates visibility_level' do
- project_param = { visibility_level: 20 }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
project_param.each_pair do |k, v|
@@ -1124,7 +1081,7 @@ describe API::Projects, api: true do
it 'updates visibility_level from public to private' do
project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
- project_param = { public: false }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
project_param.each_pair do |k, v|
@@ -1197,7 +1154,7 @@ describe API::Projects, api: true do
end
it 'does not update visibility_level' do
- project_param = { visibility_level: 20 }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(403)
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 9bfc84c7425..c0e7bab8199 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -600,6 +600,7 @@ describe 'Git LFS API and storage' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid,
'size' => sample_size,
+ 'authenticated' => true,
'actions' => {
'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
diff --git a/spec/rubocop/cop/gem_fetcher_spec.rb b/spec/rubocop/cop/gem_fetcher_spec.rb
new file mode 100644
index 00000000000..c07f6a831dc
--- /dev/null
+++ b/spec/rubocop/cop/gem_fetcher_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/gem_fetcher'
+
+describe RuboCop::Cop::GemFetcher do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in Gemfile' do
+ before do
+ allow(cop).to receive(:gemfile?).and_return(true)
+ end
+
+ it 'registers an offense when a gem uses `git`' do
+ inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['git: "https://gitlab.com/foo/bar.git"'])
+ end
+ end
+
+ it 'registers an offense when a gem uses `github`' do
+ inspect_source(cop, 'gem "foo", github: "foo/bar.git"')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['github: "foo/bar.git"'])
+ end
+ end
+ end
+
+ context 'outside of Gemfile' do
+ it 'registers no offense' do
+ inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
new file mode 100644
index 00000000000..6b9b6b19650
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_column_with_default'
+
+describe RuboCop::Cop::Migration::AddColumnWithDefault do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when add_column_with_default is used inside a change method' do
+ inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers no offense when add_column_with_default is used inside an up method' do
+ inspect_source(cop, 'def up; add_column_with_default :table, :column, default: false; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 3c37660885d..1b95f1ff198 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -52,4 +52,136 @@ describe EnvironmentSerializer do
expect(json).to be_an_instance_of Array
end
end
+
+ context 'when representing environments within folders' do
+ let(:serializer) do
+ described_class.new(project: project).within_folders
+ end
+
+ let(:resource) { Environment.all }
+
+ subject { serializer.represent(resource) }
+
+ context 'when there is a single environment' do
+ before { create(:environment, name: 'staging') }
+
+ it 'represents one standalone environment' do
+ expect(subject.count).to eq 1
+ expect(subject.first[:name]).to eq 'staging'
+ expect(subject.first[:size]).to eq 1
+ expect(subject.first[:latest][:name]).to eq 'staging'
+ end
+ end
+
+ context 'when there are multiple environments in folder' do
+ before do
+ create(:environment, name: 'staging/my-review-1')
+ create(:environment, name: 'staging/my-review-2')
+ end
+
+ it 'represents one item that is a folder' do
+ expect(subject.count).to eq 1
+ expect(subject.first[:name]).to eq 'staging'
+ expect(subject.first[:size]).to eq 2
+ expect(subject.first[:latest][:name]).to eq 'staging/my-review-2'
+ expect(subject.first[:latest][:environment_type]).to eq 'staging'
+ end
+ end
+
+ context 'when there are multiple folders and standalone environments' do
+ before do
+ create(:environment, name: 'staging/my-review-1')
+ create(:environment, name: 'staging/my-review-2')
+ create(:environment, name: 'production/my-review-3')
+ create(:environment, name: 'testing')
+ end
+
+ it 'represents multiple items grouped within folders' do
+ expect(subject.count).to eq 3
+
+ expect(subject.first[:name]).to eq 'production'
+ expect(subject.first[:size]).to eq 1
+ expect(subject.first[:latest][:name]).to eq 'production/my-review-3'
+ expect(subject.first[:latest][:environment_type]).to eq 'production'
+ expect(subject.second[:name]).to eq 'staging'
+ expect(subject.second[:size]).to eq 2
+ expect(subject.second[:latest][:name]).to eq 'staging/my-review-2'
+ expect(subject.second[:latest][:environment_type]).to eq 'staging'
+ expect(subject.third[:name]).to eq 'testing'
+ expect(subject.third[:size]).to eq 1
+ expect(subject.third[:latest][:name]).to eq 'testing'
+ expect(subject.third[:latest][:environment_type]).to be_nil
+ end
+ end
+ end
+
+ context 'when used with pagination' do
+ let(:request) { spy('request') }
+ let(:response) { spy('response') }
+ let(:resource) { Environment.all }
+ let(:pagination) { { page: 1, per_page: 2 } }
+
+ let(:serializer) do
+ described_class.new(project: project)
+ .with_pagination(request, response)
+ end
+
+ before do
+ allow(request).to receive(:query_parameters)
+ .and_return(pagination)
+ end
+
+ subject { serializer.represent(resource) }
+
+ it 'creates a paginated serializer' do
+ expect(serializer).to be_paginated
+ end
+
+ context 'when resource is paginatable relation' do
+ context 'when there is a single environment object in relation' do
+ before { create(:environment) }
+
+ it 'serializes environments' do
+ expect(subject.first).to have_key :id
+ end
+ end
+
+ context 'when multiple environment objects are serialized' do
+ before { create_list(:environment, 3) }
+
+ it 'serializes appropriate number of objects' do
+ expect(subject.count).to be 2
+ end
+
+ it 'appends relevant headers' do
+ expect(response).to receive(:[]=).with('X-Total', '3')
+ expect(response).to receive(:[]=).with('X-Total-Pages', '2')
+ expect(response).to receive(:[]=).with('X-Per-Page', '2')
+
+ subject
+ end
+ end
+
+ context 'when grouping environments within folders' do
+ let(:serializer) do
+ described_class.new(project: project)
+ .with_pagination(request, response)
+ .within_folders
+ end
+
+ before do
+ create(:environment, name: 'staging/review-1')
+ create(:environment, name: 'staging/review-2')
+ create(:environment, name: 'production/deploy-3')
+ create(:environment, name: 'testing')
+ end
+
+ it 'paginates grouped items including ordering' do
+ expect(subject.count).to eq 2
+ expect(subject.first[:name]).to eq 'production'
+ expect(subject.second[:name]).to eq 'staging'
+ end
+ end
+ end
+ end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 7cbf131e41e..2aaef03cb93 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -52,14 +52,14 @@ describe PipelineSerializer do
expect(serializer).to be_paginated
end
- context 'when resource does is not paginatable' do
+ context 'when resource is not paginatable' do
context 'when a single pipeline object is being serialized' do
let(:resource) { create(:ci_empty_pipeline) }
let(:pagination) { { page: 1, per_page: 1 } }
it 'raises error' do
- expect { subject }
- .to raise_error(PipelineSerializer::InvalidResourceError)
+ expect { subject }.to raise_error(
+ Gitlab::Serializer::Pagination::InvalidResourceError)
end
end
end
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index 538e85cdc89..f86189b68e9 100644
--- a/spec/services/destroy_group_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe DestroyGroupService, services: true do
+describe Groups::DestroyService, services: true do
include DatabaseConnectionHelpers
- let!(:user) { create(:user) }
- let!(:group) { create(:group) }
- let!(:project) { create(:project, namespace: group) }
+ let!(:user) { create(:user) }
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: group) }
let!(:gitlab_shell) { Gitlab::Shell.new }
- let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+ let!(:remove_path) { group.path + "+#{group.id}+deleted" }
shared_examples 'group destruction' do |async|
context 'database records' do
@@ -43,9 +43,9 @@ describe DestroyGroupService, services: true do
def destroy_group(group, user, async)
if async
- DestroyGroupService.new(group, user).async_execute
+ Groups::DestroyService.new(group, user).async_execute
else
- DestroyGroupService.new(group, user).execute
+ Groups::DestroyService.new(group, user).execute
end
end
end
@@ -80,7 +80,7 @@ describe DestroyGroupService, services: true do
# Kick off the initial group destroy in a new thread, so that
# it doesn't share this spec's database transaction.
- Thread.new { DestroyGroupService.new(group, user).async_execute }.join(5)
+ Thread.new { Groups::DestroyService.new(group, user).async_execute }.join(5)
group_record = run_with_new_database_connection do |conn|
conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index 1d0a747a480..f53f96e0c2b 100644
--- a/spec/services/notes/delete_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::DeleteService, services: true do
+describe Notes::DestroyService, services: true do
describe '#execute' do
it 'deletes a note' do
project = create(:empty_project)
diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/users/destroy_spec.rb
index 418a12a83a9..46e58393218 100644
--- a/spec/services/delete_user_service_spec.rb
+++ b/spec/services/users/destroy_spec.rb
@@ -1,15 +1,16 @@
require 'spec_helper'
-describe DeleteUserService, services: true do
+describe Users::DestroyService, services: true do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:current_user) { create(:user) }
let!(:namespace) { create(:namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace) }
+ let(:service) { described_class.new(current_user) }
context 'no options are given' do
it 'deletes the user' do
- user_data = DeleteUserService.new(current_user).execute(user)
+ user_data = service.execute(user)
expect { user_data['email'].to eq(user.email) }
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
@@ -19,7 +20,7 @@ describe DeleteUserService, services: true do
it 'will delete the project in the near future' do
expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once
- DeleteUserService.new(current_user).execute(user)
+ service.execute(user)
end
end
@@ -30,7 +31,7 @@ describe DeleteUserService, services: true do
before do
solo_owned.group_members = [member]
- DeleteUserService.new(current_user).execute(user)
+ service.execute(user)
end
it 'does not delete the user' do
@@ -45,7 +46,7 @@ describe DeleteUserService, services: true do
before do
solo_owned.group_members = [member]
- DeleteUserService.new(current_user).execute(user, delete_solo_owned_groups: true)
+ service.execute(user, delete_solo_owned_groups: true)
end
it 'deletes solo owned groups' do
diff --git a/spec/support/drag_to_helper.rb b/spec/support/drag_to_helper.rb
new file mode 100644
index 00000000000..0c0659d3ecd
--- /dev/null
+++ b/spec/support/drag_to_helper.rb
@@ -0,0 +1,13 @@
+module DragTo
+ def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body')
+ evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
+
+ Timeout.timeout(Capybara.default_max_wait_time) do
+ loop until drag_active?
+ end
+ end
+
+ def drag_active?
+ page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').zero?
+ end
+end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 14c56521280..0765573408c 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -5,14 +5,14 @@ describe DeleteUserWorker do
let!(:current_user) { create(:user) }
it "calls the DeleteUserWorker with the params it was given" do
- expect_any_instance_of(DeleteUserService).to receive(:execute).
+ expect_any_instance_of(Users::DestroyService).to receive(:execute).
with(user, {})
DeleteUserWorker.new.perform(current_user.id, user.id)
end
it "uses symbolized keys" do
- expect_any_instance_of(DeleteUserService).to receive(:execute).
+ expect_any_instance_of(Users::DestroyService).to receive(:execute).
with(user, test: "test")
DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test")