summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-11-05 02:17:40 +0800
committerLin Jen-Shin <godfat@godfat.org>2016-11-05 02:17:40 +0800
commit64e8b7ca49b6e00d7a5e218260e836b675c380e0 (patch)
tree46d43bdd1a287070f8b91e5ff95271713e94bcb4
parentf16dca30516281aee428e78708cd91825ea7b75d (diff)
parent86b8fb4e7f376bdb70faf8ce99f34b6ec0fc8dc1 (diff)
downloadgitlab-ce-64e8b7ca49b6e00d7a5e218260e836b675c380e0.tar.gz
Merge remote-tracking branch 'upstream/master' into pipeline-notifications
* upstream/master: (74 commits) Fetch locals to avoid undefined method/local error Remove author according to the document Simplify implementation of entity serializers Add documentation for the "Only allow merge requests to be merged if all discussions are resolved" feature Complete and improve specs Add setting to only allow merge requests to be merged when all discussions are resolved Apply `*_params_ce + *_params_ee` pattern to MergeRequestsController Add tests for deployment and environment entitites Add tests for serialization entities, add user entity Only skip group when it's actually a group in the "Share with group" select Fix: Todos Filter Shows All Users Fix: Guest sees some repository details and gets 404 Move shared params to a helper GrapeDSL for project hooks Update commits.scss updated styling commit SHA on branches page + added to changelog change build list height to show 6,5 builds + improve padding of list, with first/last child selectors Refine build entity tests a little Expose commit author if author exists Ignore builds directory from eslint ...
-rw-r--r--.eslintignore2
-rw-r--r--.eslintrc2
-rw-r--r--CHANGELOG.md10
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock12
-rw-r--r--README.md4
-rw-r--r--app/assets/javascripts/api.js12
-rw-r--r--app/assets/javascripts/application.js178
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es614
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/groups_select.js7
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js75
-rw-r--r--app/assets/javascripts/members.js.es62
-rw-r--r--app/assets/javascripts/project_new.js13
-rw-r--r--app/assets/javascripts/project_select.js4
-rw-r--r--app/assets/javascripts/search.js2
-rw-r--r--app/assets/javascripts/users_select.js4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/selects.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss17
-rw-r--r--app/assets/stylesheets/pages/editor.scss4
-rw-r--r--app/assets/stylesheets/pages/icons.scss12
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss15
-rw-r--r--app/assets/stylesheets/pages/status.scss3
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/application_controller.rb3
-rw-r--r--app/controllers/autocomplete_controller.rb6
-rw-r--r--app/controllers/projects/commits_controller.rb9
-rw-r--r--app/controllers/projects/group_links_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb41
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/ci_status_helper.rb18
-rw-r--r--app/helpers/commits_helper.rb8
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/models/application_setting.rb39
-rw-r--r--app/models/commit.rb15
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/user.rb1
-rw-r--r--app/serializers/base_serializer.rb18
-rw-r--r--app/serializers/build_entity.rb24
-rw-r--r--app/serializers/commit_entity.rb12
-rw-r--r--app/serializers/deployment_entity.rb27
-rw-r--r--app/serializers/entity_request.rb12
-rw-r--r--app/serializers/environment_entity.rb20
-rw-r--r--app/serializers/environment_serializer.rb3
-rw-r--r--app/serializers/request_aware_entity.rb11
-rw-r--r--app/serializers/user_entity.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/admin/logs/show.html.haml2
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/issues.html.haml4
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/groups/issues.atom.builder2
-rw-r--r--app/views/groups/issues.html.haml4
-rw-r--r--app/views/layouts/devise.html.haml4
-rw-r--r--app/views/projects/_last_commit.html.haml10
-rw-r--r--app/views/projects/_merge_request_settings.html.haml4
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml8
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/commits/_commit_list.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml6
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/issues/index.html.haml4
-rw-r--r--app/views/projects/merge_requests/branch_from.html.haml3
-rw-r--r--app/views/projects/merge_requests/branch_to.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml6
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/shared/icons/_icon_status_canceled.svg (renamed from app/views/shared/icons/_icon_status_cancel.svg)2
-rw-r--r--app/views/shared/icons/_icon_status_created.svg2
-rw-r--r--app/views/shared/icons/_icon_status_skipped.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml1
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml4
-rw-r--r--changelogs/unreleased/22588-todos-filter-shows-all-users.yml4
-rw-r--r--changelogs/unreleased/22947-fix_issues_atom_feed_url.yml4
-rw-r--r--changelogs/unreleased/23961-can-t-share-project-with-groups.yml4
-rw-r--r--changelogs/unreleased/24056-guest-sees-some-project-details-and-gets-404.yml4
-rw-r--r--changelogs/unreleased/24059-round-robin-repository-storage.yml4
-rw-r--r--changelogs/unreleased/24102-cannot-unselect-remove-source-branch-when-editing-merge-request.yml4
-rw-r--r--changelogs/unreleased/fix-invalid-filename-eslint.yml4
-rw-r--r--changelogs/unreleased/show-status-from-branch.yml4
-rw-r--r--db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb17
-rw-r--r--db/migrate/20161103171205_rename_repository_storage_column.rb29
-rw-r--r--db/schema.rb5
-rw-r--r--doc/administration/img/repository_storages_admin_ui.pngbin17081 -> 54043 bytes
-rw-r--r--doc/administration/logs.md3
-rw-r--r--doc/administration/repository_storages.md3
-rw-r--r--doc/api/groups.md8
-rw-r--r--doc/api/projects.md10
-rw-r--r--doc/api/settings.md4
-rw-r--r--doc/ci/docker/using_docker_build.md2
-rw-r--r--doc/development/doc_styleguide.md1
-rw-r--r--doc/development/frontend.md6
-rw-r--r--doc/development/rake_tasks.md8
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/university/README.md22
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/university/support/README.md36
-rw-r--r--doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.pngbin0 -> 24693 bytes
-rw-r--r--doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.pngbin0 -> 6940 bytes
-rw-r--r--doc/user/project/merge_requests/merge_request_discussion_resolution.md18
-rw-r--r--doc/user/project/merge_requests/merge_when_build_succeeds.md4
-rw-r--r--features/steps/admin/logs.rb2
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/groups.rb3
-rw-r--r--lib/api/labels.rb6
-rw-r--r--lib/api/project_hooks.rb153
-rw-r--r--lib/api/projects.rb9
-rw-r--r--lib/api/settings.rb4
-rw-r--r--lib/gitlab/environment_logger.rb (renamed from lib/gitlab/production_logger.rb)4
-rw-r--r--lib/tasks/gitlab/generate_docs.rake7
-rw-r--r--spec/bin/changelog_spec.rb56
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb66
-rw-r--r--spec/factories/merge_requests.rb5
-rw-r--r--spec/features/admin/admin_runners_spec.rb27
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb11
-rw-r--r--spec/features/atom/issues_spec.rb29
-rw-r--r--spec/features/commits_spec.rb31
-rw-r--r--spec/features/dashboard_issues_spec.rb20
-rw-r--r--spec/features/issues/filter_issues_spec.rb34
-rw-r--r--spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb69
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb14
-rw-r--r--spec/features/projects/builds_spec.rb38
-rw-r--r--spec/features/projects/features_visibility_spec.rb15
-rw-r--r--spec/features/todos/todos_filtering_spec.rb53
-rw-r--r--spec/javascripts/merge_request_widget_spec.js2
-rw-r--r--spec/models/application_setting_spec.rb56
-rw-r--r--spec/models/commit_spec.rb51
-rw-r--r--spec/models/merge_request_spec.rb59
-rw-r--r--spec/models/project_spec.rb15
-rw-r--r--spec/models/user_spec.rb14
-rw-r--r--spec/requests/api/groups_spec.rb13
-rw-r--r--spec/requests/api/labels_spec.rb15
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb32
-rw-r--r--spec/requests/api/merge_requests_spec.rb16
-rw-r--r--spec/requests/api/projects_spec.rb36
-rw-r--r--spec/requests/api/settings_spec.rb1
-rw-r--r--spec/requests/ci/api/builds_spec.rb25
-rw-r--r--spec/requests/ci/api/runners_spec.rb10
-rw-r--r--spec/serializers/build_entity_spec.rb31
-rw-r--r--spec/serializers/commit_entity_spec.rb44
-rw-r--r--spec/serializers/deployment_entity_spec.rb20
-rw-r--r--spec/serializers/entity_request_spec.rb18
-rw-r--r--spec/serializers/environment_entity_spec.rb18
-rw-r--r--spec/serializers/environment_serializer_spec.rb60
-rw-r--r--spec/serializers/user_entity_spec.rb23
-rw-r--r--vendor/assets/javascripts/jquery.timeago.js (renamed from app/assets/javascripts/lib/utils/jquery.timeago.js)0
160 files changed, 1710 insertions, 558 deletions
diff --git a/.eslintignore b/.eslintignore
index a57137b4d70..d9c2233c9d7 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,4 +2,4 @@
/public/
/tmp/
/vendor/
-
+/builds/
diff --git a/.eslintrc b/.eslintrc
index b58007d90a9..fd26215b843 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -4,7 +4,7 @@
"filenames"
],
"rules": {
- "filenames/match-regex": [2, "^[a-z_]+$"]
+ "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
},
"globals": {
"$": false,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7efa9efa4f8..9411cc62003 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ entry.
## 8.14.0 (2016-11-22)
+- Show correct environment log in admin/logs (@duk3luk3 !7191)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
- Adds user project membership expired event to clarify why user was removed (Callum Dryden)
@@ -17,6 +18,7 @@ entry.
- Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
+- Cleaned up global namespace JS !19661 (Jose Ivan Vargas)
- Refactor Jira service to use jira-ruby gem
- Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne)
@@ -26,6 +28,7 @@ entry.
- Fix sidekiq stats in admin area (blackst0ne)
- Added label description as tooltip to issue board list title
- Created cycle analytics bundle JavaScript file
+- Hides container registry when repository is disabled
- API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne)
@@ -45,6 +48,8 @@ entry.
- New issue board list dropdown stays open after adding a new list
- Fix: Backup restore doesn't clear cache
- Optimize Event queries by removing default order
+- Add new icon for skipped builds
+- Show created icon in pipeline mini-graph
- Remove duplicate links from sidebar
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
- Add Rake task to create/repair GitLab Shell hooks symlinks !5634
@@ -62,11 +67,16 @@ entry.
- Improve search query parameter naming in /admin/users !7115 (YarNayar)
- Fix table pagination to be responsive
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
+- Updated commit SHA styling on the branches page.
## 8.13.3 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
+- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
+- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
+- Fix project features default values
+- Changed build dropdown list length to be 6,5 builds long in the pipeline graph
## 8.13.2 (2016-10-31)
diff --git a/Gemfile b/Gemfile
index af82ae16a56..de624256c16 100644
--- a/Gemfile
+++ b/Gemfile
@@ -100,11 +100,11 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
-gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
+gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'gitlab-markup', '~> 1.5.0'
gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2'
-gem 'rdoc', '~>3.6'
+gem 'rdoc', '~> 4.2'
gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
@@ -260,9 +260,6 @@ group :development do
gem 'better_errors', '~> 1.0.1'
gem 'binding_of_caller', '~> 0.7.2'
- # Docs generator
- gem 'sdoc', '~> 0.3.20'
-
# thin instead webrick
gem 'thin', '~> 1.7.0'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 888fa6b2bf5..6ea0578d9d2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -159,7 +159,7 @@ GEM
database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
- deckar01-task_list (1.0.5)
+ deckar01-task_list (1.0.6)
activesupport (~> 4.0)
html-pipeline
rack (~> 1.0)
@@ -567,7 +567,7 @@ GEM
ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
- rdoc (3.12.2)
+ rdoc (4.2.2)
json (~> 1.4)
recaptcha (3.0.0)
json
@@ -663,9 +663,6 @@ GEM
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
- sdoc (0.3.20)
- json (>= 1.1.3)
- rdoc (~> 3.10)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
@@ -843,7 +840,7 @@ DEPENDENCIES
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0)
- deckar01-task_list (= 1.0.5)
+ deckar01-task_list (= 1.0.6)
default_value_for (~> 3.0.0)
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
@@ -936,7 +933,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rblineprof (~> 0.3.6)
- rdoc (~> 3.6)
+ rdoc (~> 4.2)
recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
@@ -956,7 +953,6 @@ DEPENDENCIES
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
- sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 2.0.0)
diff --git a/README.md b/README.md
index 46492f4b9c8..dbe6db3ebed 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Redis 2.8+
- MySQL or PostgreSQL
-For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
+For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
## Third-party applications
@@ -96,7 +96,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/
## Documentation
-All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
+All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ce/).
## Getting help
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 7ebe1599fca..1cab66e109e 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -22,16 +22,14 @@
});
},
// Return groups list. Filtered by query
- // Only active groups retrieved
- groups: function(query, skip_ldap, skip_groups, callback) {
+ groups: function(query, options, callback) {
var url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url: url,
- data: {
- search: query,
- skip_groups: skip_groups,
- per_page: 20
- },
+ data: $.extend({
+ search: query,
+ per_page: 20
+ }, options),
dataType: "json"
}).done(function(groups) {
return callback(groups);
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 7dd9adac736..7d942de0184 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -13,6 +13,7 @@
/*= require jquery-ui/sortable */
/*= require jquery_ujs */
/*= require jquery.endless-scroll */
+/*= require jquery.timeago */
/*= require jquery.highlight */
/*= require jquery.waitforimages */
/*= require jquery.atwho */
@@ -54,125 +55,53 @@
/*= require_directory . */
/*= require fuzzaldrin-plus */
-(function() {
- window.slugify = function(text) {
- return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
- };
-
- window.ajaxGet = function(url) {
- return $.ajax({
- type: "GET",
- url: url,
- dataType: "script"
- });
- };
-
- window.split = function(val) {
- return val.split(/,\s*/);
- };
-
- window.extractLast = function(term) {
- return split(term).pop();
- };
-
- window.rstrip = function(val) {
- if (val) {
- return val.replace(/\s+$/, '');
- } else {
- return val;
- }
- };
-
- // Disable button if text field is empty
- window.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
- event_name = event_name || 'input';
- var closest_submit, field;
- field = $(field_selector);
- closest_submit = field.closest('form').find(button_selector);
- if (rstrip(field.val()) === "") {
- closest_submit.disable();
- }
- return field.on(event_name, function() {
- if (rstrip($(this).val()) === "") {
- return closest_submit.disable();
- } else {
- return closest_submit.enable();
- }
- });
- };
+(function () {
+ document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
+ window.addEventListener('hashchange', gl.utils.shiftWindow);
- // Disable button if any input field with given selector is empty
- window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
- var closest_submit, updateButtons;
- closest_submit = form.find(button_selector);
- updateButtons = function() {
- var filled;
- filled = true;
- form.find('input').filter(form_selector).each(function() {
- return filled = rstrip($(this).val()) !== "" || !$(this).attr('required');
- });
- if (filled) {
- return closest_submit.enable();
- } else {
- return closest_submit.disable();
- }
- };
- updateButtons();
- return form.keyup(updateButtons);
- };
-
- window.sanitize = function(str) {
- return str.replace(/<(?:.|\n)*?>/gm, '');
- };
-
- window.shiftWindow = function() {
- return scrollBy(0, -100);
- };
-
- document.addEventListener("page:fetch", gl.utils.cleanupBeforeFetch);
-
- window.addEventListener("hashchange", shiftWindow);
-
- window.onload = function() {
+ window.onload = function () {
// Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768
if (location.hash) {
- return setTimeout(shiftWindow, 100);
+ return setTimeout(gl.utils.shiftWindow, 100);
}
};
- $(function() {
- var $body, $document, $sidebarGutterToggle, $window, bootstrapBreakpoint, checkInitialSidebarSize, fitSidebarForSize, flash;
- $document = $(document);
- $window = $(window);
- $body = $('body');
+ $(function () {
+ var $body = $('body');
+ var $document = $(document);
+ var $window = $(window);
+ var $sidebarGutterToggle = $('.js-sidebar-toggle');
+ var $flash = $('.flash-container');
+ var bootstrapBreakpoint = bp.getBreakpointSize();
+ var checkInitialSidebarSize;
+ var fitSidebarForSize;
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
gl.utils.preventDisabledButtons();
- bootstrapBreakpoint = bp.getBreakpointSize();
- $(".nav-sidebar").niceScroll({
+ $('.nav-sidebar').niceScroll({
cursoropacitymax: '0.4',
cursorcolor: '#FFF',
- cursorborder: "1px solid #FFF"
+ cursorborder: '1px solid #FFF'
});
- $(".js-select-on-focus").on("focusin", function() {
- return $(this).select().one('mouseup', function(e) {
+ $('.js-select-on-focus').on('focusin', function () {
+ return $(this).select().one('mouseup', function (e) {
return e.preventDefault();
});
// Click a .js-select-on-focus field, select the contents
// Prevent a mouseup event from deselecting the input
});
- $('.remove-row').bind('ajax:success', function() {
+ $('.remove-row').bind('ajax:success', function () {
$(this).tooltip('destroy')
.closest('li')
.fadeOut();
});
- $('.js-remove-tr').bind('ajax:before', function() {
+ $('.js-remove-tr').bind('ajax:before', function () {
return $(this).hide();
});
- $('.js-remove-tr').bind('ajax:success', function() {
+ $('.js-remove-tr').bind('ajax:success', function () {
return $(this).closest('tr').fadeOut();
});
$('select.select2').select2({
@@ -180,8 +109,8 @@
// Initialize select2 selects
dropdownAutoWidth: true
});
- $('.js-select2').bind('select2-close', function() {
- return setTimeout((function() {
+ $('.js-select2').bind('select2-close', function () {
+ return setTimeout((function () {
$('.select2-container-active').removeClass('select2-container-active');
return $(':focus').blur();
}), 1);
@@ -191,24 +120,24 @@
$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
- placement: function(_, el) {
+ placement: function (_, el) {
return $(el).data('placement') || 'bottom';
}
});
- $('.trigger-submit').on('change', function() {
+ $('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
});
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Flash
- if ((flash = $(".flash-container")).length > 0) {
- flash.click(function() {
+ if ($flash.length > 0) {
+ $flash.click(function () {
return $(this).fadeOut();
});
- flash.show();
+ $flash.show();
}
// Disable form buttons while a form is submitting
- $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
+ $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons;
buttons = $('[type="submit"]', this);
switch (e.type) {
@@ -219,36 +148,36 @@
return buttons.enable();
}
});
- $(document).ajaxError(function(e, xhrObj, xhrSetting, xhrErrorText) {
- var ref;
+ $(document).ajaxError(function (e, xhrObj) {
+ var ref = xhrObj.status;
if (xhrObj.status === 401) {
return new Flash('You need to be logged in.', 'alert');
- } else if ((ref = xhrObj.status) === 404 || ref === 500) {
+ } else if (ref === 404 || ref === 500) {
return new Flash('Something went wrong on our end.', 'alert');
}
});
- $('.account-box').hover(function() {
+ $('.account-box').hover(function () {
// Show/Hide the profile menu when hovering the account box
return $(this).toggleClass('hover');
});
- $document.on('click', '.diff-content .js-show-suppressed-diff', function() {
+ $document.on('click', '.diff-content .js-show-suppressed-diff', function () {
var $container;
$container = $(this).parent();
$container.next('table').show();
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', function() {
+ $('.navbar-toggle').on('click', function () {
$('.header-content .title').toggle();
$('.header-content .header-logo').toggle();
$('.header-content .navbar-collapse').toggle();
return $('.navbar-toggle').toggleClass('active');
});
// Show/hide comments on diff
- $body.on("click", ".js-toggle-diff-comments", function(e) {
+ $body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
- $this.toggleClass('active');
var notesHolders = $this.closest('.diff-file').find('.notes_holder');
+ $this.toggleClass('active');
if ($this.hasClass('active')) {
notesHolders.show().find('.hide').show();
} else {
@@ -257,30 +186,27 @@
$this.trigger('blur');
return e.preventDefault();
});
- $document.off("click", '.js-confirm-danger');
- $document.on("click", '.js-confirm-danger', function(e) {
- var btn, form, text;
+ $document.off('click', '.js-confirm-danger');
+ $document.on('click', '.js-confirm-danger', function (e) {
+ var btn = $(e.target);
+ var form = btn.closest('form');
+ var text = btn.data('confirm-danger-message');
e.preventDefault();
- btn = $(e.target);
- text = btn.data("confirm-danger-message");
- form = btn.closest("form");
return new ConfirmDangerModal(form, text);
});
- $document.on('click', 'button', function() {
+ $document.on('click', 'button', function () {
return $(this).blur();
});
- $('input[type="search"]').each(function() {
- var $this;
- $this = $(this);
+ $('input[type="search"]').each(function () {
+ var $this = $(this);
$this.attr('value', $this.val());
});
- $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function(e) {
+ $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function () {
var $this;
$this = $(this);
return $this.attr('value', $this.val());
});
- $sidebarGutterToggle = $('.js-sidebar-toggle');
- $document.off('breakpoint:change').on('breakpoint:change', function(e, breakpoint) {
+ $document.off('breakpoint:change').on('breakpoint:change', function (e, breakpoint) {
var $gutterIcon;
if (breakpoint === 'sm' || breakpoint === 'xs') {
$gutterIcon = $sidebarGutterToggle.find('i');
@@ -289,7 +215,7 @@
}
}
});
- fitSidebarForSize = function() {
+ fitSidebarForSize = function () {
var oldBootstrapBreakpoint;
oldBootstrapBreakpoint = bootstrapBreakpoint;
bootstrapBreakpoint = bp.getBreakpointSize();
@@ -297,13 +223,13 @@
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
}
};
- checkInitialSidebarSize = function() {
+ checkInitialSidebarSize = function () {
bootstrapBreakpoint = bp.getBreakpointSize();
- if (bootstrapBreakpoint === "xs" || "sm") {
+ if (bootstrapBreakpoint === 'xs' || 'sm') {
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
}
};
- $window.off("resize.app").on("resize.app", function(e) {
+ $window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize();
});
gl.awardsHandler = new AwardsHandler();
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 8bdb0965f99..d7cda977845 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,7 +1,7 @@
/* eslint-disable */
(function() {
this.AwardsHandler = (function() {
- const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
+ var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
function AwardsHandler() {
this.aliases = gl.emojiAliases();
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index 230a1b95c52..143d21adb37 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -12,7 +12,7 @@
submit.disable();
$('.js-confirm-danger-input').off('input');
$('.js-confirm-danger-input').on('input', function() {
- if (rstrip($(this).val()) === project_path) {
+ if (gl.utils.rstrip($(this).val()) === project_path) {
return submit.enable();
} else {
return submit.disable();
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 31df51ac03a..824413bf20f 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -126,8 +126,8 @@
}
return {
username: m.username,
- title: sanitize(title),
- search: sanitize(m.username + " " + m.name)
+ title: gl.utils.sanitize(title),
+ search: gl.utils.sanitize(m.username + " " + m.name)
};
});
}
@@ -159,7 +159,7 @@
}
return {
id: i.iid,
- title: sanitize(i.title),
+ title: gl.utils.sanitize(i.title),
search: i.iid + " " + i.title
};
});
@@ -189,7 +189,7 @@
}
return {
id: m.iid,
- title: sanitize(m.title),
+ title: gl.utils.sanitize(m.title),
search: "" + m.title
};
});
@@ -222,7 +222,7 @@
}
return {
id: m.iid,
- title: sanitize(m.title),
+ title: gl.utils.sanitize(m.title),
search: m.iid + " " + m.title
};
});
@@ -240,9 +240,9 @@
var sanitizeLabelTitle;
sanitizeLabelTitle = function(title) {
if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
- return "\"" + (sanitize(title)) + "\"";
+ return "\"" + (gl.utils.sanitize(title)) + "\"";
} else {
- return sanitize(title);
+ return gl.utils.sanitize(title);
}
};
return $.map(merges, function(m) {
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 742807d93ad..ce54c34492d 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -24,8 +24,8 @@
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
- disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
// remove notify commit author checkbox for non-commit notes
+ gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index b275620c799..e3c39c895ba 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -6,15 +6,16 @@
function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) {
return function(i, select) {
- var skip_ldap, skip_groups;
- skip_ldap = $(select).hasClass('skip_ldap');
+ var all_available, skip_groups;
+ all_available = $(select).data('all-available');
skip_groups = $(select).data('skip-groups') || [];
return $(select).select2({
placeholder: "Search for a group",
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
- return Api.groups(query.term, skip_ldap, skip_groups, function(groups) {
+ options = { all_available: all_available, skip_groups: skip_groups };
+ return Api.groups(query.term, options, function(groups) {
var data;
data = {
results: groups
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 21efe2d76dd..8447421195d 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -24,6 +24,81 @@
return null;
}
};
+
+ w.gl.utils.ajaxGet = function(url) {
+ return $.ajax({
+ type: "GET",
+ url: url,
+ dataType: "script"
+ });
+ };
+
+ w.gl.utils.split = function(val) {
+ return val.split(/,\s*/);
+ };
+
+ w.gl.utils.extractLast = function(term) {
+ return this.split(term).pop();
+ };
+
+ w.gl.utils.rstrip = function rstrip(val) {
+ if (val) {
+ return val.replace(/\s+$/, '');
+ } else {
+ return val;
+ }
+ };
+
+ w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
+ event_name = event_name || 'input';
+ var closest_submit, field, that;
+ that = this;
+ field = $(field_selector);
+ closest_submit = field.closest('form').find(button_selector);
+ if (this.rstrip(field.val()) === "") {
+ closest_submit.disable();
+ }
+ return field.on(event_name, function() {
+ if (that.rstrip($(this).val()) === "") {
+ return closest_submit.disable();
+ } else {
+ return closest_submit.enable();
+ }
+ });
+ };
+
+ w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
+ var closest_submit, updateButtons;
+ closest_submit = form.find(button_selector);
+ updateButtons = function() {
+ var filled;
+ filled = true;
+ form.find('input').filter(form_selector).each(function() {
+ return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required');
+ });
+ if (filled) {
+ return closest_submit.enable();
+ } else {
+ return closest_submit.disable();
+ }
+ };
+ updateButtons();
+ return form.keyup(updateButtons);
+ };
+
+ w.gl.utils.sanitize = function(str) {
+ return str.replace(/<(?:.|\n)*?>/gm, '');
+ };
+
+ w.gl.utils.unbindEvents = function() {
+ return $(document).off('scroll');
+ };
+
+ w.gl.utils.shiftWindow = function() {
+ return w.scrollBy(0, -100);
+ };
+
+
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
};
diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6
index 371abd09e78..895bc10784f 100644
--- a/app/assets/javascripts/members.js.es6
+++ b/app/assets/javascripts/members.js.es6
@@ -11,7 +11,7 @@
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit);
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
- disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
+ gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
removeRow(e) {
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 40575caa57f..0d3fb31a9cf 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -45,7 +45,9 @@
};
ProjectNew.prototype.toggleRepoVisibility = function () {
- var $repoAccessLevel = $('.js-repo-access-level select');
+ var $repoAccessLevel = $('.js-repo-access-level select'),
+ containerRegistry = document.querySelectorAll('.js-container-registry')[0],
+ containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
@@ -70,8 +72,17 @@
if (selectedVal) {
this.$repoSelects.removeClass('disabled');
+
+ if (containerRegistry) {
+ containerRegistry.style.display = '';
+ }
} else {
this.$repoSelects.addClass('disabled');
+
+ if (containerRegistry) {
+ containerRegistry.style.display = 'none';
+ containerRegistryCheckbox.checked = false;
+ }
}
}.bind(this));
};
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index b74b4ae68ff..e1acf3c8232 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -24,7 +24,7 @@
data = groups.concat(projects);
return finalCallback(data);
};
- return Api.groups(term, false, false, groupsCallback);
+ return Api.groups(term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
@@ -73,7 +73,7 @@
data = groups.concat(projects);
return finalCallback(data);
};
- return Api.groups(query.term, false, false, groupsCallback);
+ return Api.groups(query.term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 6c2389f202f..d79e6f014f6 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -11,7 +11,7 @@
filterable: true,
fieldName: 'group_id',
data: function(term, callback) {
- return Api.groups(term, false, false, function(data) {
+ return Api.groups(term, {}, function(data) {
data.unshift({
name: 'Any'
});
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 3847278e80a..7a2221dbaf5 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -23,6 +23,8 @@
$dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id');
options.showCurrentUser = $dropdown.data('current-user');
+ options.todoFilter = $dropdown.data('todo-filter');
+ options.todoStateFilter = $dropdown.data('todo-state-filter');
showNullUser = $dropdown.data('null-user');
showMenuAbove = $dropdown.data('showMenuAbove');
showAnyUser = $dropdown.data('any-user');
@@ -394,6 +396,8 @@
project_id: options.projectId || null,
group_id: options.groupId || null,
skip_ldap: options.skipLdap || null,
+ todo_filter: options.todoFilter || null,
+ todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null,
push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
author_id: options.authorId || null,
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index baa38ab60c8..3e34ec98427 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -36,7 +36,7 @@
color: $dropdown-toggle-color;
font-size: 15px;
text-align: left;
- border: 1px solid $dropdown-toggle-border-color;
+ border: 1px solid $border-color;
border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
@@ -45,11 +45,9 @@
.fa {
position: absolute;
- top: 50%;
- right: 6px;
- margin-top: -6px;
+ top: 10px;
+ right: 8px;
color: $dropdown-toggle-icon-color;
- font-size: 10px;
&.fa-spinner {
font-size: 16px;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index ecdf0be1a05..13749f1b7bd 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -27,9 +27,9 @@
height: 0;
margin-left: 2px;
vertical-align: middle;
- border-top: $caret-width-base dashed;
- border-right: $caret-width-base solid transparent;
- border-left: $caret-width-base solid transparent;
+ border-top: 5px dashed;
+ border-right: 5px solid transparent;
+ border-left: 5px solid transparent;
color: $gray-darkest;
}
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 52d6a39bd59..98a84351a3d 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -164,7 +164,22 @@
.branch-commit {
color: $gl-gray;
- .commit-id,
+ .commit-icon {
+ text-align: center;
+ display: inline-block;
+
+ svg {
+ height: 14px;
+ width: 14px;
+ vertical-align: middle;
+ fill: $table-text-gray;
+ }
+ }
+
+ .commit-id {
+ color: $gl-link-color;
+ }
+
.commit-row-message {
color: $gl-gray;
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index cb8cefaca97..778126bcfb7 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -55,6 +55,10 @@
float: left;
}
+ .file-buttons {
+ font-size: 0;
+ }
+
.select2 {
float: right;
}
diff --git a/app/assets/stylesheets/pages/icons.scss b/app/assets/stylesheets/pages/icons.scss
new file mode 100644
index 00000000000..407c8db211d
--- /dev/null
+++ b/app/assets/stylesheets/pages/icons.scss
@@ -0,0 +1,12 @@
+// CI icon colors
+
+.ci-status-icon {
+ &-created {
+ fill: $gray-darkest;
+ }
+
+ &-skipped,
+ &-canceled {
+ fill: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a8e8bbcb208..bf3cb6e7ad9 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -85,6 +85,11 @@
}
.commit-link {
+ a {
+ &:focus {
+ text-decoration: none;
+ }
+ }
.ci-status {
@@ -439,7 +444,7 @@
}
.grouped-pipeline-dropdown {
- padding: 8px 0;
+ padding: 0;
width: 186px;
left: auto;
right: -197px;
@@ -448,6 +453,14 @@
ul {
max-height: 245px;
overflow: auto;
+
+ li:first-child {
+ padding-top: 8px;
+ }
+
+ li:last-child {
+ padding-bottom: 8px;
+ }
}
a {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 01426e28e92..92997eae8b9 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -6,7 +6,8 @@
white-space: nowrap;
border-radius: 4px;
- &:hover {
+ &:hover,
+ &:focus {
text-decoration: none;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 6ef7cf0bae6..86e808314f4 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -116,8 +116,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_packet_size,
:send_user_confirmation_email,
:container_registry_token_expire_delay,
- :repository_storage,
:enabled_git_access_protocol,
+ repository_storages: [],
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 37600ed875c..517ad4f03f3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -192,9 +192,10 @@ class ApplicationController < ActionController::Base
end
# JSON for infinite scroll via Pager object
- def pager_json(partial, count)
+ def pager_json(partial, count, locals = {})
html = render_to_string(
partial,
+ locals: locals,
layout: false,
formats: [:html]
)
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index b48668eea87..daa82336208 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -11,9 +11,13 @@ class AutocompleteController < ApplicationController
@users = @users.reorder(:name)
@users = @users.page(params[:page])
+ if params[:todo_filter].present?
+ @users = @users.todo_authors(current_user.id, params[:todo_state_filter])
+ end
+
if params[:search].blank?
# Include current user if available to filter by "Me"
- if params[:current_user] && current_user
+ if params[:current_user].present? && current_user
@users = [*@users, current_user]
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index c2e7bf1ffec..aba87b6144b 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -26,8 +26,15 @@ class Projects::CommitsController < Projects::ApplicationController
respond_to do |format|
format.html
- format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false }
+
+ format.json do
+ pager_json(
+ 'projects/commits/_commits',
+ @commits.size,
+ project: @project,
+ ref: @ref)
+ end
end
end
end
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index ae060abee5c..9eaf26a0dbf 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -7,7 +7,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
@group_links = project.project_group_links.all
@skip_groups = @group_links.pluck(:group_id)
- @skip_groups << project.group.try(:id)
+ @skip_groups << project.namespace_id unless project.personal?
end
def create
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 30f1cf4e5be..9f104d903cc 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -352,13 +352,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def branch_from
# This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
- @commit = @repository.commit(params[:ref]) if params[:ref].present?
+
+ if params[:ref].present?
+ @ref = params[:ref]
+ @commit = @repository.commit(@ref)
+ end
+
render layout: false
end
def branch_to
@target_project = selected_target_project
- @commit = @target_project.commit(params[:ref]) if params[:ref].present?
+
+ if params[:ref].present?
+ @ref = params[:ref]
+ @commit = @target_project.commit(@ref)
+ end
+
render layout: false
end
@@ -589,12 +599,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_request_params
- params.require(:merge_request).permit(
- :title, :assignee_id, :source_project_id, :source_branch,
- :target_project_id, :target_branch, :milestone_id,
- :state_event, :description, :task_num, :force_remove_source_branch,
- :lock_version, label_ids: []
- )
+ params.require(:merge_request)
+ .permit(merge_request_params_ce)
+ end
+
+ def merge_request_params_ce
+ [
+ :assignee_id,
+ :description,
+ :force_remove_source_branch,
+ :lock_version,
+ :milestone_id,
+ :source_branch,
+ :source_project_id,
+ :state_event,
+ :target_branch,
+ :target_project_id,
+ :task_num,
+ :title,
+
+ label_ids: []
+ ]
end
def merge_params
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index bce5e29d8d8..6988527a3be 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -335,6 +335,7 @@ class ProjectsController < Projects::ApplicationController
:visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
+ :only_allow_merge_if_all_discussions_are_resolved,
:lfs_enabled, project_feature_attributes
)
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 6229384817b..45a567a1eba 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -93,11 +93,11 @@ module ApplicationSettingsHelper
end
end
- def repository_storage_options_for_select
+ def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name]
end
- options_for_select(options, @application_setting.repository_storage)
+ options_for_select(options, @application_setting.repository_storages)
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index b7f48630bd4..895c3d728ad 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -47,17 +47,27 @@ module CiStatusHelper
'icon_play'
when 'created'
'icon_status_created'
+ when 'skipped'
+ 'icon_status_skipped'
else
- 'icon_status_cancel'
+ 'icon_status_canceled'
end
custom_icon(icon_name)
end
- def render_commit_status(commit, tooltip_placement: 'auto left')
+ def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
- path = pipelines_namespace_project_commit_path(project.namespace, project, commit)
- render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
+ path = pipelines_namespace_project_commit_path(
+ project.namespace,
+ project,
+ commit)
+
+ render_status_with_link(
+ 'commit',
+ commit.status(ref),
+ path,
+ tooltip_placement: tooltip_placement)
end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 33dcee49aee..ed402b698fb 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -25,9 +25,11 @@ module CommitsHelper
end
end
- def commit_to_html(commit, project, inline = true)
- template = inline ? "inline_commit" : "commit"
- render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
+ def commit_to_html(commit, ref, project)
+ render 'projects/commits/commit',
+ commit: commit,
+ ref: ref,
+ project: project
end
# Breadcrumb links for a Project and, if applicable, a tree path
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 81e0b6bb5ae..cbab1fd5967 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -43,7 +43,7 @@ module DropdownsHelper
default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
- output << icon('chevron-down')
+ output << icon('caret-down')
output.html_safe
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c99aa7772bb..6e7a90e7d9c 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,6 +18,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :disabled_oauth_sign_in_sources, Array
serialize :domain_whitelist, Array
serialize :domain_blacklist, Array
+ serialize :repository_storages
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
@@ -74,9 +75,8 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
- validates :repository_storage,
- presence: true,
- inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
+ validates :repository_storages, presence: true
+ validate :check_repository_storages
validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
@@ -166,7 +166,7 @@ class ApplicationSetting < ActiveRecord::Base
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
container_registry_token_expire_delay: 5,
- repository_storage: 'default',
+ repository_storages: ['default'],
user_default_external: false,
)
end
@@ -201,6 +201,29 @@ class ApplicationSetting < ActiveRecord::Base
self.domain_blacklist_raw = file.read
end
+ def repository_storages
+ value = read_attribute(:repository_storages)
+ value = [value] if value.is_a?(String)
+ value = [] if value.nil?
+
+ value
+ end
+
+ # repository_storage is still required in the API. Remove in 9.0
+ def repository_storage
+ repository_storages.first
+ end
+
+ def repository_storage=(value)
+ self.repository_storages = [value]
+ end
+
+ # Choose one of the available repository storage options. Currently all have
+ # equal weighting.
+ def pick_repository_storage
+ repository_storages.sample
+ end
+
def runners_registration_token
ensure_runners_registration_token!
end
@@ -208,4 +231,12 @@ class ApplicationSetting < ActiveRecord::Base
def health_check_access_token
ensure_health_check_access_token!
end
+
+ private
+
+ def check_repository_storages
+ invalid = repository_storages - Gitlab.config.repositories.storages.keys
+ errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
+ invalid.empty?
+ end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index e64fd1e0c1b..9e7fde9503d 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -226,12 +226,19 @@ class Commit
end
def pipelines
- @pipeline ||= project.pipelines.where(sha: sha)
+ project.pipelines.where(sha: sha)
end
- def status
- return @status if defined?(@status)
- @status ||= pipelines.status
+ def status(ref = nil)
+ @statuses ||= {}
+
+ if @statuses.key?(ref)
+ @statuses[ref]
+ elsif ref
+ @statuses[ref] = pipelines.where(ref: ref).status
+ else
+ @statuses[ref] = pipelines.status
+ end
end
def revert_branch_name
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0397c57f935..d76feb9680e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -425,6 +425,7 @@ class MergeRequest < ActiveRecord::Base
return false if work_in_progress?
return false if broken?
return false unless skip_ci_check || mergeable_ci_state?
+ return false unless mergeable_discussions_state?
true
end
@@ -441,11 +442,11 @@ class MergeRequest < ActiveRecord::Base
end
def should_remove_source_branch?
- merge_params['should_remove_source_branch'].present?
+ Gitlab::Utils.to_boolean(merge_params['should_remove_source_branch'])
end
def force_remove_source_branch?
- merge_params['force_remove_source_branch'].present?
+ Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end
def remove_source_branch?
@@ -493,6 +494,12 @@ class MergeRequest < ActiveRecord::Base
discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?)
end
+ def mergeable_discussions_state?
+ return true unless project.only_allow_merge_if_all_discussions_are_resolved?
+
+ discussions_resolved?
+ end
+
def hook_attrs
attrs = {
source: source_project.try(:hook_attrs),
diff --git a/app/models/project.rb b/app/models/project.rb
index d5512dfaf9c..686d285410b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,7 +28,7 @@ class Project < ActiveRecord::Base
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
- default_value_for(:repository_storage) { current_application_settings.repository_storage }
+ default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
@@ -1067,10 +1067,6 @@ class Project < ActiveRecord::Base
forks.count
end
- def find_label(name)
- labels.find_by(name: name)
- end
-
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index af3c0b7dc02..65e96ee6b2e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -173,6 +173,7 @@ class User < ActiveRecord::Base
scope :active, -> { with_state(:active) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
+ scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
new file mode 100644
index 00000000000..de9a181db90
--- /dev/null
+++ b/app/serializers/base_serializer.rb
@@ -0,0 +1,18 @@
+class BaseSerializer
+ def initialize(parameters = {})
+ @request = EntityRequest.new(parameters)
+ end
+
+ def represent(resource, opts = {})
+ self.class.entity_class
+ .represent(resource, opts.merge(request: @request))
+ end
+
+ def self.entity(entity_class)
+ @entity_class ||= entity_class
+ end
+
+ def self.entity_class
+ @entity_class
+ end
+end
diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb
new file mode 100644
index 00000000000..3d9ac66de0e
--- /dev/null
+++ b/app/serializers/build_entity.rb
@@ -0,0 +1,24 @@
+class BuildEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :name
+
+ expose :build_url do |build|
+ url_to(:namespace_project_build, build)
+ end
+
+ expose :retry_url do |build|
+ url_to(:retry_namespace_project_build, build)
+ end
+
+ expose :play_url, if: ->(build, _) { build.manual? } do |build|
+ url_to(:play_namespace_project_build, build)
+ end
+
+ private
+
+ def url_to(route, build)
+ send("#{route}_url", build.project.namespace, build.project, build)
+ end
+end
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
new file mode 100644
index 00000000000..f7eba6fc1e3
--- /dev/null
+++ b/app/serializers/commit_entity.rb
@@ -0,0 +1,12 @@
+class CommitEntity < API::Entities::RepoCommit
+ include RequestAwareEntity
+
+ expose :author, using: UserEntity
+
+ expose :commit_url do |commit|
+ namespace_project_tree_url(
+ request.project.namespace,
+ request.project,
+ id: commit.id)
+ end
+end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
new file mode 100644
index 00000000000..ad6fc8d665b
--- /dev/null
+++ b/app/serializers/deployment_entity.rb
@@ -0,0 +1,27 @@
+class DeploymentEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :iid
+ expose :sha
+
+ expose :ref do
+ expose :name do |deployment|
+ deployment.ref
+ end
+
+ expose :ref_url do |deployment|
+ namespace_project_tree_url(
+ deployment.project.namespace,
+ deployment.project,
+ id: deployment.ref)
+ end
+ end
+
+ expose :tag
+ expose :last?
+ expose :user, using: UserEntity
+ expose :commit, using: CommitEntity
+ expose :deployable, using: BuildEntity
+ expose :manual_actions, using: BuildEntity
+end
diff --git a/app/serializers/entity_request.rb b/app/serializers/entity_request.rb
new file mode 100644
index 00000000000..456ba1174c0
--- /dev/null
+++ b/app/serializers/entity_request.rb
@@ -0,0 +1,12 @@
+class EntityRequest
+ # We use EntityRequest object to collect parameters and variables
+ # from the controller. Because options that are being passed to the entity
+ # do appear in each entity object in the chain, we need a way to pass data
+ # that is present in the controller (see #20045).
+ #
+ def initialize(parameters)
+ parameters.each do |key, value|
+ define_singleton_method(key) { value }
+ end
+ end
+end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
new file mode 100644
index 00000000000..ee4392cc46d
--- /dev/null
+++ b/app/serializers/environment_entity.rb
@@ -0,0 +1,20 @@
+class EnvironmentEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :name
+ expose :state
+ expose :external_url
+ expose :environment_type
+ expose :last_deployment, using: DeploymentEntity
+ expose :stoppable?
+
+ expose :environment_url do |environment|
+ namespace_project_environment_url(
+ environment.project.namespace,
+ environment.project,
+ environment)
+ end
+
+ expose :created_at, :updated_at
+end
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
new file mode 100644
index 00000000000..91955542f25
--- /dev/null
+++ b/app/serializers/environment_serializer.rb
@@ -0,0 +1,3 @@
+class EnvironmentSerializer < BaseSerializer
+ entity EnvironmentEntity
+end
diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb
new file mode 100644
index 00000000000..ff8c1142abc
--- /dev/null
+++ b/app/serializers/request_aware_entity.rb
@@ -0,0 +1,11 @@
+module RequestAwareEntity
+ extend ActiveSupport::Concern
+
+ included do
+ include Gitlab::Routing.url_helpers
+ end
+
+ def request
+ @options.fetch(:request)
+ end
+end
diff --git a/app/serializers/user_entity.rb b/app/serializers/user_entity.rb
new file mode 100644
index 00000000000..43754ea94f7
--- /dev/null
+++ b/app/serializers/user_entity.rb
@@ -0,0 +1,2 @@
+class UserEntity < API::Entities::UserBasic
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index c4c68cd7891..28003e5f509 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -353,9 +353,9 @@
%fieldset
%legend Repository Storage
.form-group
- = f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2'
+ = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10
- = f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control'
+ = f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control'
.help-block
Manage repository storage paths. Learn more in the
= succeed "." do
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 676812121d7..824edd171f3 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
- Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
+ Gitlab::EnvironmentLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger]
= render 'admin/background_jobs/head'
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 0404d0728ea..bdea1064096 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
- xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
+ xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 1eec4db45a0..3caaf827ff5 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -2,13 +2,13 @@
- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
- if current_user
- = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
+ = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{current_user.name} issues")
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
- = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+ = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 2411cc45724..e247eebc3fc 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -37,7 +37,7 @@
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
- placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
+ placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } })
.filter-item.inline
- if params[:type].present?
= hidden_field_tag(:type, params[:type])
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index b1628040325..0cc6466d34e 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@group.name} issues"
- xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
+ xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 4434f1cbd35..dc6c1bb69de 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,13 +1,13 @@
- page_title "Issues"
= content_for :meta_tags do
- if current_user
- = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
+ = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues")
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
- = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+ = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 6922f1e153f..afd9958f073 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -26,8 +26,8 @@
Perform code reviews and enhance collaboration with merge requests.
Each project can also have an issue tracker and a wiki.
- - if current_application_settings.sign_in_text.present?
- = markdown_field(current_application_settings, :sign_in_text)
+ - if current_application_settings.sign_in_text.present?
+ = markdown_field(current_application_settings, :sign_in_text)
%hr.footer-fixed
.container.footer-container
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 630ae7d6140..8e23d51b224 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,7 +1,9 @@
-- if commit.status
- = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
- = ci_icon_for_status(commit.status)
- = ci_label_for_status(commit.status)
+- ref = local_assigns.fetch(:ref)
+- status = commit.status(ref)
+- if status
+ = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{status}" do
+ = ci_icon_for_status(status)
+ = ci_label_for_status(status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index 80053dd501b..6e143c4b570 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -12,3 +12,7 @@
%span.descr
Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+ .checkbox
+ = f.label :only_allow_merge_if_all_discussions_are_resolved do
+ = f.check_box :only_allow_merge_if_all_discussions_are_resolved
+ %strong Only allow merge requests to be merged if all discussions are resolved
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 3ffc3fcb7ac..149ee7c59d6 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -20,7 +20,7 @@
%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
- = render blob_commit, project: @project
+ = render blob_commit, project: @project, ref: @ref
%div#blob-content-holder.blob-content-holder
%article.file-holder
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index d4f59764a70..4a6aa92e3f3 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -14,13 +14,13 @@
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
- .pull-right
+ .pull-right.file-buttons
.license-selector.js-license-selector-wrap.hidden
- = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Choose a License template", options: { toggle_class: 'btn js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden
- = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
- = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
= button_tag class: 'soft-wrap-toggle btn', type: 'button' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index b1f50eb5f34..57a27ec904e 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -26,6 +26,6 @@
:javascript
- disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
+ gl.utils.disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
new NewCommitForm($('.js-upload-blob-form'))
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index d54c76ff9c8..de607772df6 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,4 +1,6 @@
.branch-commit
+ .icon-container.commit-icon
+ = custom_icon("icon_commit")
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
&middot;
%span.str-truncated
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 840f468dc05..1f748d73d06 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -41,7 +41,7 @@
- else
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.relevant.latest.stages_status
+ - stages_status = pipeline.statuses.latest.stages_status
%td.stage-cell
- stages.each do |stage|
- status = stages_status[stage]
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index fb48aef0559..9f80a974d64 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,3 +1,4 @@
+- ref = local_assigns.fetch(:ref)
- if @note_counts
- note_count = @note_counts.fetch(commit.id, 0)
- else
@@ -18,15 +19,15 @@
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
- - if commit.status
+ - if commit.status(ref)
.visible-xs-inline
- = render_commit_status(commit)
+ = render_commit_status(commit, ref: ref)
- if commit.description?
%a.text-expander.hidden-xs.js-toggle-button ...
.commit-actions.hidden-xs
- - if commit.status
- = render_commit_status(commit)
+ - if commit.status(ref)
+ = render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index 46e4de40042..ce416caa494 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -11,4 +11,4 @@
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
- %ul.content-list= render commits, project: @project
+ %ul.content-list= render commits, project: @project, ref: @ref
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index dd12eae8f7e..48756c68941 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,13 +1,11 @@
-- unless defined?(project)
- - project = @project
-
+- ref = local_assigns.fetch(:ref)
- commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
%li.commits-row
%ul.list-unstyled.commit-list
- = render commits, project: project
+ = render commits, project: project, ref: ref
- if hidden > 0
%li.alert.alert-warning
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 876c8002627..9628cbd1634 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -35,7 +35,7 @@
%div{id: dom_id(@project)}
%ol#commits-list.list-unstyled.content_list
- = render "commits", project: @project
+ = render 'commits', project: @project, ref: @ref
= spinner
:javascript
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c40ad06969e..a5422966617 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -102,7 +102,7 @@
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- if Gitlab.config.registry.enabled
- .form-group
+ .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox
= f.label :container_registry_enabled do
= f.check_box :container_registry_enabled
@@ -290,4 +290,4 @@
Saving project.
%p Please wait a moment, this page will automatically refresh when ready.
-= render 'shared/confirm_modal', phrase: @project.path \ No newline at end of file
+= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 36957560de0..a0df0db77c5 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} issues"
- xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
+ xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index cc57cfdb342..c493ff3585b 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -8,7 +8,7 @@
= content_for :meta_tags do
- if current_user
- = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
+ = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@project.name} issues")
%div{ class: (container_class) }
- if @project.issues.any?
@@ -16,7 +16,7 @@
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do
= icon('rss')
%span.icon-label
Subscribe
diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml
index 4f90dde6fa8..3837c4b388d 100644
--- a/app/views/projects/merge_requests/branch_from.html.haml
+++ b/app/views/projects/merge_requests/branch_from.html.haml
@@ -1 +1,2 @@
-= commit_to_html(@commit, @source_project, false)
+- if @commit
+ = commit_to_html(@commit, @ref, @source_project)
diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml
index 67a7a6bcec9..d69b71790a0 100644
--- a/app/views/projects/merge_requests/branch_to.html.haml
+++ b/app/views/projects/merge_requests/branch_to.html.haml
@@ -1 +1,2 @@
-= commit_to_html(@commit, @target_project, false)
+- if @commit
+ = commit_to_html(@commit, @ref, @target_project)
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 61020516bcf..a0e12fb3f38 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -3,4 +3,4 @@
Most recent commits displayed first
%ol#commits-list.list-unstyled
- = render "projects/commits/commits", project: @merge_request.source_project
+ = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 842b6df310d..01314eb37d0 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -23,8 +23,10 @@
= render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed'
- - elsif !@merge_request.mergeable_ci_state? && @pipeline && @pipeline.failed?
+ - elsif !@merge_request.mergeable_ci_state?
= render 'projects/merge_requests/widget/open/build_failed'
+ - elsif !@merge_request.mergeable_discussions_state?
+ = render 'projects/merge_requests/widget/open/unresolved_discussions'
- elsif @merge_request.can_be_merged? || resolved_conflicts
= render 'projects/merge_requests/widget/open/accept'
diff --git a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml
new file mode 100644
index 00000000000..35d5677ee37
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml
@@ -0,0 +1,6 @@
+%h4
+ = icon('exclamation-triangle')
+ This merge request has unresolved discussions
+
+%p
+ Please resolve these discussions to allow this merge request to be merged. \ No newline at end of file
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index ba16c641462..4de95036eef 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,7 +12,7 @@
= render 'projects/last_push'
= render "home_panel"
-- if @project.feature_available?(:repository, current_user)
+- if current_user && can?(current_user, :download_code, @project)
%nav.project-stats{ class: container_class }
%ul.nav
%li
@@ -79,7 +79,7 @@
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
- = render 'projects/last_commit', commit: @repository.commit, project: @project
+ = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class }
- if @project.archived?
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 0f7d629ab98..21e378b8735 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -37,5 +37,5 @@
:javascript
// Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() {
- ajaxGet("#{escape_javascript(@logs_path)}");
+ gl.utils.ajaxGet("#{escape_javascript(@logs_path)}");
});
diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_canceled.svg
index fd1ebbcbabd..1b2d0891244 100644
--- a/app/views/shared/icons/_icon_status_cancel.svg
+++ b/app/views/shared/icons/_icon_status_canceled.svg
@@ -1,4 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" class="ci-status-icon-canceled" viewBox="0 0 14 14">
<g fill="#5C5C5C" fill-rule="evenodd">
<path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
<rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg
index 1f5c3b51b03..dca5d289767 100644
--- a/app/views/shared/icons/_icon_status_created.svg
+++ b/app/views/shared/icons/_icon_status_created.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" class="ci-status-icon-created" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
diff --git a/app/views/shared/icons/_icon_status_skipped.svg b/app/views/shared/icons/_icon_status_skipped.svg
new file mode 100644
index 00000000000..014ca86b61b
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_skipped.svg
@@ -0,0 +1 @@
+<svg width="20" height="20" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg>
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index d410755cad1..0ace6be8f4e 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -142,6 +142,7 @@
.col-sm-10.col-sm-offset-2
.checkbox
= label_tag 'merge_request[force_remove_source_branch]' do
+ = hidden_field_tag 'merge_request[force_remove_source_branch]', '0'
= check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
Remove source branch when merge request is accepted.
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 22b5a6aa11b..1d778bc88de 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -22,7 +22,7 @@
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) }
= multi_label_name(selected, "Labels")
- = icon('chevron-down')
+ = icon('caret-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
diff --git a/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml b/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml
new file mode 100644
index 00000000000..8f03746ff80
--- /dev/null
+++ b/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml
@@ -0,0 +1,4 @@
+---
+title: Add setting to only allow merge requests to be merged when all discussions are resolved
+merge_request: 7125
+author: Rodolfo Arruda
diff --git a/changelogs/unreleased/22588-todos-filter-shows-all-users.yml b/changelogs/unreleased/22588-todos-filter-shows-all-users.yml
new file mode 100644
index 00000000000..1da72142880
--- /dev/null
+++ b/changelogs/unreleased/22588-todos-filter-shows-all-users.yml
@@ -0,0 +1,4 @@
+---
+title: 'Fix: Todos Filter Shows All Users'
+merge_request:
+author:
diff --git a/changelogs/unreleased/22947-fix_issues_atom_feed_url.yml b/changelogs/unreleased/22947-fix_issues_atom_feed_url.yml
new file mode 100644
index 00000000000..2312afdb3d7
--- /dev/null
+++ b/changelogs/unreleased/22947-fix_issues_atom_feed_url.yml
@@ -0,0 +1,4 @@
+---
+title: Issues atom feed url reflect filters on dashboard
+merge_request: 7114
+author: Lucas Deschamps
diff --git a/changelogs/unreleased/23961-can-t-share-project-with-groups.yml b/changelogs/unreleased/23961-can-t-share-project-with-groups.yml
new file mode 100644
index 00000000000..b3bfcbda4b7
--- /dev/null
+++ b/changelogs/unreleased/23961-can-t-share-project-with-groups.yml
@@ -0,0 +1,4 @@
+---
+title: Only skip group when it's actually a group in the "Share with group" select
+merge_request: 7262
+author:
diff --git a/changelogs/unreleased/24056-guest-sees-some-project-details-and-gets-404.yml b/changelogs/unreleased/24056-guest-sees-some-project-details-and-gets-404.yml
new file mode 100644
index 00000000000..8ca0c5beab3
--- /dev/null
+++ b/changelogs/unreleased/24056-guest-sees-some-project-details-and-gets-404.yml
@@ -0,0 +1,4 @@
+---
+title: 'Fix: Guest sees some repository details and gets 404'
+merge_request:
+author:
diff --git a/changelogs/unreleased/24059-round-robin-repository-storage.yml b/changelogs/unreleased/24059-round-robin-repository-storage.yml
new file mode 100644
index 00000000000..109536114ff
--- /dev/null
+++ b/changelogs/unreleased/24059-round-robin-repository-storage.yml
@@ -0,0 +1,4 @@
+---
+title: Introduce round-robin project creation to spread load over multiple shards
+merge_request: 7266
+author:
diff --git a/changelogs/unreleased/24102-cannot-unselect-remove-source-branch-when-editing-merge-request.yml b/changelogs/unreleased/24102-cannot-unselect-remove-source-branch-when-editing-merge-request.yml
new file mode 100644
index 00000000000..50d018170f1
--- /dev/null
+++ b/changelogs/unreleased/24102-cannot-unselect-remove-source-branch-when-editing-merge-request.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure merge request's "remove branch" accessors return booleans
+merge_request: 7267
+author:
diff --git a/changelogs/unreleased/fix-invalid-filename-eslint.yml b/changelogs/unreleased/fix-invalid-filename-eslint.yml
new file mode 100644
index 00000000000..eea21149c90
--- /dev/null
+++ b/changelogs/unreleased/fix-invalid-filename-eslint.yml
@@ -0,0 +1,4 @@
+---
+title: Fix invalid filename validation on eslint
+merge_request: 7281
+author:
diff --git a/changelogs/unreleased/show-status-from-branch.yml b/changelogs/unreleased/show-status-from-branch.yml
new file mode 100644
index 00000000000..1afc230c05c
--- /dev/null
+++ b/changelogs/unreleased/show-status-from-branch.yml
@@ -0,0 +1,4 @@
+---
+title: Fix showing pipeline status for a given commit from correct branch
+merge_request: 7034
+author:
diff --git a/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb b/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb
new file mode 100644
index 00000000000..fad62d716b3
--- /dev/null
+++ b/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb
@@ -0,0 +1,17 @@
+class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:projects,
+ :only_allow_merge_if_all_discussions_are_resolved,
+ :boolean,
+ default: false)
+ end
+
+ def down
+ remove_column(:projects, :only_allow_merge_if_all_discussions_are_resolved)
+ end
+end
diff --git a/db/migrate/20161103171205_rename_repository_storage_column.rb b/db/migrate/20161103171205_rename_repository_storage_column.rb
new file mode 100644
index 00000000000..e9f992793b4
--- /dev/null
+++ b/db/migrate/20161103171205_rename_repository_storage_column.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameRepositoryStorageColumn < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ rename_column :application_settings, :repository_storage, :repository_storages
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 54b5fc83be0..5476b0c93e5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161025231710) do
+ActiveRecord::Schema.define(version: 20161103171205) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -88,7 +88,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.integer "container_registry_token_expire_delay", default: 5
t.text "after_sign_up_text"
t.boolean "user_default_external", default: false, null: false
- t.string "repository_storage", default: "default"
+ t.string "repository_storages", default: "default"
t.string "enabled_git_access_protocol"
t.boolean "domain_blacklist_enabled", default: false
t.text "domain_blacklist"
@@ -905,6 +905,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.boolean "has_external_wiki"
t.boolean "lfs_enabled"
t.text "description_html"
+ t.boolean "only_allow_merge_if_all_discussions_are_resolved", default: false, null: false
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
diff --git a/doc/administration/img/repository_storages_admin_ui.png b/doc/administration/img/repository_storages_admin_ui.png
index 599350bc098..6481baca1ad 100644
--- a/doc/administration/img/repository_storages_admin_ui.png
+++ b/doc/administration/img/repository_storages_admin_ui.png
Binary files differ
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 737b39db16c..d757a3c2a66 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -13,7 +13,8 @@ This guide talks about how to read and use these system log files.
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
omnibus package or in `/home/git/gitlab/log/production.log` for
-installations from source.
+installations from source. (When Gitlab is running in an environment
+other than production, the corresponding logfile is shown here.)
It contains information about all performed requests. You can see the
URL and type of request, IP address and what exactly parts of code were
diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md
index 55b054fc1a4..ab70557b69a 100644
--- a/doc/administration/repository_storages.md
+++ b/doc/administration/repository_storages.md
@@ -91,6 +91,9 @@ be stored via the **Application Settings** in the Admin area.
![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
+Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
+randomly placed on one of the selected paths.
+
[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
[restart gitlab]: restart_gitlab.md#installations-from-source
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/api/groups.md b/doc/api/groups.md
index e81d6f9de4b..b56d74d25e0 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -2,7 +2,12 @@
## List groups
-Get a list of groups. (As user: my groups, as admin: all groups)
+Get a list of groups. (As user: my groups or all available, as admin: all groups).
+
+Parameters:
+
+- `all_available` (optional) - if passed, show all groups you have access to
+- `skip_groups` (optional)(array of group IDs) - if passed, skip groups
```
GET /groups
@@ -21,7 +26,6 @@ GET /groups
You can search for groups by name or path, see below.
-
## List a group's projects
Get a list of projects in this group.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 4f4b20a1874..bbb3bfb4995 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -89,6 +89,7 @@ Parameters:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
},
{
@@ -151,6 +152,7 @@ Parameters:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
]
@@ -429,6 +431,7 @@ Parameters:
}
],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
```
@@ -602,6 +605,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -634,6 +638,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -665,6 +670,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -752,6 +758,7 @@ Example response:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
```
@@ -820,6 +827,7 @@ Example response:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
```
@@ -908,6 +916,7 @@ Example response:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
```
@@ -996,6 +1005,7 @@ Example response:
"public_builds": true,
"shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
}
```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index f7ad3b4cc8e..218546aafea 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -42,6 +42,7 @@ Example response:
"sign_in_text" : null,
"container_registry_token_expire_delay": 5,
"repository_storage": "default",
+ "repository_storages": ["default"],
"koding_enabled": false,
"koding_url": null
}
@@ -73,7 +74,8 @@ PUT /application/settings
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
| `after_sign_out_path` | string | no | Where to redirect users after logout |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
-| `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml |
+| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
+| `repository_storage` | string | no | The first entry in `repository_storages`. Deprecated, but retained for compatibility reasons |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index a313c31e7ee..959741f7338 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -226,7 +226,7 @@ e.g. `docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_ap
> **Note:**
This feature requires GitLab 8.8 and GitLab Runner 1.2.
-Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../container_registry/README.md). For example, if you're using
+Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../user/project/container_registry.md). For example, if you're using
docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look:
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 2cfa30f652e..b137e6ae82e 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -465,6 +465,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
+[ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index ece8f880542..1d7d9127a64 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -196,6 +196,12 @@ It consists of two subtasks:
As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
(and saves you some time).
+If you need to debug your tests and/or application code while they're
+running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon)
+in your browser, open DevTools, and run tests for individual files by clicking
+on them. This is also much faster than setting up and running tests from the
+command line.
+
Please note: Not all of the frontend fixtures are generated. Some are still static
files. These will not be touched by `rake teaspoon:fixtures`.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index a7175f3f87e..827db7e99b8 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -42,14 +42,6 @@ To run several tests inside one directory:
If you want to use [Spring](https://github.com/rails/spring) set
`ENABLE_SPRING=1` in your environment.
-## Generate searchable docs for source code
-
-You can find results under the `doc/code` directory.
-
-```
-bundle exec rake gitlab:generate_docs
-```
-
## Generate API documentation for project services (e.g. Slack)
```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 83090f46271..7e947e4b2ba 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -479,10 +479,14 @@ Copy the example site config:
sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
-Make sure to edit the config file to match your setup:
+Make sure to edit the config file to match your setup. Also, ensure that you match your paths to GitLab, especially if installing for a user other than the 'git' user:
# Change YOUR_SERVER_FQDN to the fully-qualified
# domain name of your host serving GitLab.
+ #
+ # Remember to match your paths to GitLab, especially
+ # if installing for a user other than 'git'.
+ #
# If using Ubuntu default nginx install:
# either remove the default_server from the listen line
# or else sudo rm -f /etc/nginx/sites-enabled/default
diff --git a/doc/university/README.md b/doc/university/README.md
index f5a0dab39fe..510b753f70d 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -2,7 +2,7 @@
GitLab University is the best place to learn about **Version Control with Git and GitLab**.
-It doesn't replace, but accompanies our great [Documentation](http://docs.gitlab.com)
+It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
and [Blog Articles](https://about.gitlab.com/blog/).
Would you like to contribute to GitLab University? Then please take a look at our contribution [process](/process) for more information.
@@ -31,7 +31,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [An Overview of GitLab.com - Video](https://www.youtube.com/watch?v=WaiL5DGEMR4)
1. [Why Use Git and GitLab - Slides](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/edit?usp=drive_web)
-1. [GitLab Basics - Article](http://doc.gitlab.com/ce/gitlab-basics/README.html)
+1. [GitLab Basics - Article](../gitlab-basics/README.md)
1. [Git and GitLab Basics - Video](https://www.youtube.com/watch?v=03wb9FvO4Ak&index=5&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [Git and GitLab Basics - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-23370/material/)
1. [Comparison of GitLab Versions](https://about.gitlab.com/features/#compare)
@@ -51,10 +51,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control
-1. [Migrating from BitBucket/Stash](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html)
-1. [Migrating from GitHub](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_github.html)
-1. [Migrating from SVN](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
-1. [Migrating from Fogbugz](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html)
+1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html)
+1. [Migrating from GitHub](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_github.html)
+1. [Migrating from SVN](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
+1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html)
#### 1.6. GitLab Inc.
@@ -91,11 +91,11 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
-1. [GitLab Pages Documentation](http://doc.gitlab.com/ee/pages/README.html)
+1. [GitLab Pages Documentation](https://docs.gitlab.com/ee/pages/README.html)
#### 2.2. GitLab Issues
-1. [Markdown in GitLab](http://doc.gitlab.com/ce/markdown/markdown.html)
+1. [Markdown in GitLab](../user/markdown.md)
1. [Issues and Merge Requests - Video](https://www.youtube.com/watch?v=raXvuwet78M)
1. [Due Dates and Milestones fro GitLab Issues](https://about.gitlab.com/2016/08/05/feature-highlight-set-dates-for-issues/)
1. [How to Use GitLab Labels](https://about.gitlab.com/2016/08/17/using-gitlab-labels/)
@@ -129,7 +129,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA)
1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/)
1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)
-1. [GitLab Flow Documentation](http://doc.gitlab.com/ee/workflow/gitlab_flow.html)
+1. [GitLab Flow Documentation](https://docs.gitlab.com/ee/workflow/gitlab_flow.html)
#### 2.5. GitLab Comparisons
@@ -189,8 +189,8 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.9. <a name="integrations"></a> Integrations
1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
-1. [How to Integrate Jira with GitLab](http://doc.gitlab.com/ee/integration/jira.html)
-1. [How to Integrate Jenkins with GitLab](http://doc.gitlab.com/ee/integration/jenkins.html)
+1. [How to Integrate Jira with GitLab](https://docs.gitlab.com/ee/integration/jira.html)
+1. [How to Integrate Jenkins with GitLab](https://docs.gitlab.com/ee/integration/jenkins.html)
1. [How to Integrate Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md)
1. [How to Integrate Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md)
1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index cf836667fac..20e7ea1987f 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -10,7 +10,7 @@ User authentication by combination of 2 different steps during login. This allow
### Access Levels
-Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](http://doc.gitlab.com/ce/permissions/permissions.html)
+Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](../../permissions/permissions.md
### Active Directory (AD)
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index da991e56370..6e415e4d219 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -58,28 +58,28 @@ Sometimes we need to upgrade customers from old versions of GitLab to latest, so
- Users
- Groups
- Projects
- - [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md)
- [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md)
- [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md)
- [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md)
- - [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- - [Perform the MySQL to PostgreSQL migration to convert your backup](http://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
- - [Upgrade to Omnibus 7.14](http://doc.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
- - [Restore backup using our Restore rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
+ - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - [Perform the MySQL to PostgreSQL migration to convert your backup](https://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
+ - [Upgrade to Omnibus 7.14](https://docs.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
+ - [Restore backup using our Restore rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
- [Upgrade to latest EE](https://about.gitlab.com/downloads-ee)
- (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
-- Perform a downgrade from [EE to CE](http://doc.gitlab.com/ee/downgrade_ee_to_ce/README.html)
+- Perform a downgrade from [EE to CE](https://docs.gitlab.com/ee/downgrade_ee_to_ce/README.html)
#### Start to learn about some of the integrations that we support
Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved
- Learn about our Integrations (specially, not only):
- - [LDAP](http://doc.gitlab.com/ee/integration/ldap.html)
- - [JIRA](http://doc.gitlab.com/ee/project_services/jira.html)
- - [Jenkins](http://doc.gitlab.com/ee/integration/jenkins.html)
- - [SAML](http://doc.gitlab.com/ce/integration/saml.html)
+ - [LDAP](https://docs.gitlab.com/ee/integration/ldap.html)
+ - [JIRA](https://docs.gitlab.com/ee/project_services/jira.html)
+ - [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html)
+ - [SAML](https://docs.gitlab.com/ce/integration/saml.html)
#### Goals
@@ -91,8 +91,8 @@ Our integrations add great value to GitLab. User questions often relate to integ
#### Understand the gathering of diagnostics for GitLab instances
- Learn about the GitLab checks that are available
- - [Environment Information and maintenance checks](http://docs.gitlab.com/ce/raketasks/maintenance.html)
- - [GitLab check](http://docs.gitlab.com/ce/raketasks/check.html)
+ - [Environment Information and maintenance checks](https://docs.gitlab.com/ce/raketasks/maintenance.html)
+ - [GitLab check](https://docs.gitlab.com/ce/raketasks/check.html)
- Omnibus commands
- [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
- [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
@@ -167,12 +167,12 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
-- Set up and try [Git Annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
-- Set up and try [Git LFS](http://doc.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
-- Get to know the [GitLab API](http://doc.gitlab.com/ee/api/README.html), its capabilities and shortcomings
-- Learn how to [migrate from SVN to Git](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
-- Set up [GitLab CI](http://doc.gitlab.com/ee/ci/quick_start/README.html)
-- Create your first [GitLab Page](http://doc.gitlab.com/ee/pages/administration.html)
+- Set up and try [Git Annex](https://docs.gitlab.com/ee/workflow/git_annex.html)
+- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
+- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
+- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
+- Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html)
+- Create your first [GitLab Page](https://docs.gitlab.com/ee/pages/administration.html)
- Get to know the GitLab Codebase by reading through the source code:
- Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png
new file mode 100644
index 00000000000..52c8acf15e0
--- /dev/null
+++ b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png
new file mode 100644
index 00000000000..79ba5c362c7
--- /dev/null
+++ b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_request_discussion_resolution.md b/doc/user/project/merge_requests/merge_request_discussion_resolution.md
index 2559f5f5250..285b1798ac5 100644
--- a/doc/user/project/merge_requests/merge_request_discussion_resolution.md
+++ b/doc/user/project/merge_requests/merge_request_discussion_resolution.md
@@ -33,7 +33,25 @@ resolved discussions tracker.
!["3/4 discussions resolved"][discussions-resolved]
+## Only allow merge requests to be merged if all discussions are resolved
+
+> [Introduced][ce-7125] in GitLab 8.14.
+
+You can prevent merge requests from being merged until all discussions are resolved.
+
+Navigate to your project's settings page, select the
+**Only allow merge requests to be merged if all discussions are resolved** check
+box and hit **Save** for the changes to take effect.
+
+![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png)
+
+From now on, you will not be able to merge from the UI until all discussions
+are resolved.
+
+![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
+
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
+[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png
diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md
index c138061fd40..d4e5b5de685 100644
--- a/doc/user/project/merge_requests/merge_when_build_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md
@@ -40,7 +40,7 @@ hit **Save** for the changes to take effect.
![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png)
-From now on, every time the pipelinefails you will not be able to merge the
+From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant builds pass.
-![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
+![Only allow merge if build succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
diff --git a/features/steps/admin/logs.rb b/features/steps/admin/logs.rb
index f9e49588c75..63881d69146 100644
--- a/features/steps/admin/logs.rb
+++ b/features/steps/admin/logs.rb
@@ -4,7 +4,7 @@ class Spinach::Features::AdminLogs < Spinach::FeatureSteps
include SharedAdmin
step 'I should see tabs with available logs' do
- expect(page).to have_content 'production.log'
+ expect(page).to have_content 'test.log'
expect(page).to have_content 'githost.log'
expect(page).to have_content 'application.log'
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index d52496451a2..01e31f6f7d1 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -100,6 +100,7 @@ module API
end
expose :only_allow_merge_if_build_succeeds
expose :request_access_enabled
+ expose :only_allow_merge_if_all_discussions_are_resolved
end
class Member < UserBasic
@@ -509,6 +510,7 @@ module API
expose :after_sign_out_path
expose :container_registry_token_expire_delay
expose :repository_storage
+ expose :repository_storages
expose :koding_enabled
expose :koding_url
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index bfb89475025..a13e353b7f5 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -8,11 +8,14 @@ module API
#
# Parameters:
# skip_groups (optional) - Array of group ids to exclude from list
+ # all_available (optional, boolean) - Show all group that you have access to
# Example Request:
# GET /groups
get do
@groups = if current_user.admin
Group.all
+ elsif params[:all_available]
+ GroupsFinder.new.execute(current_user)
else
current_user.groups
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 326e1e7ae00..238cea00fba 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -25,7 +25,7 @@ module API
post ':id/labels' do
authorize! :admin_label, user_project
- label = user_project.find_label(params[:name])
+ label = available_labels.find_by(title: params[:name])
conflict!('Label already exists') if label
label = user_project.labels.create(declared(params, include_parent_namespaces: false).to_h)
@@ -46,7 +46,7 @@ module API
delete ':id/labels' do
authorize! :admin_label, user_project
- label = user_project.find_label(params[:name])
+ label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
present label.destroy, with: Entities::Label, current_user: current_user
@@ -65,7 +65,7 @@ module API
put ':id/labels' do
authorize! :admin_label, user_project
- label = user_project.find_label(params[:name])
+ label = user_project.labels.find_by(title: params[:name])
not_found!('Label not found') unless label
update_params = declared(params,
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index dd93a85dc54..eef343c2ac6 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -1,114 +1,99 @@
module API
# Projects API
class ProjectHooks < Grape::API
+ helpers do
+ params :project_hook_properties do
+ requires :url, type: String, desc: "The URL to send the request to"
+ optional :push_events, type: Boolean, desc: "Trigger hook on push events"
+ optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
+ optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
+ optional :build_events, type: Boolean, desc: "Trigger hook on build events"
+ optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
+ optional :wiki_events, type: Boolean, desc: "Trigger hook on wiki events"
+ optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
+ optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
+ end
+ end
+
before { authenticate! }
before { authorize_admin_project }
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
resource :projects do
- # Get project hooks
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/hooks
+ desc 'Get project hooks' do
+ success Entities::ProjectHook
+ end
get ":id/hooks" do
- @hooks = paginate user_project.hooks
- present @hooks, with: Entities::ProjectHook
+ hooks = paginate user_project.hooks
+
+ present hooks, with: Entities::ProjectHook
end
- # Get a project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # Example Request:
- # GET /projects/:id/hooks/:hook_id
+ desc 'Get a project hook' do
+ success Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of a project hook'
+ end
get ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::ProjectHook
+ hook = user_project.hooks.find(params[:hook_id])
+ present hook, with: Entities::ProjectHook
end
- # Add hook to project
- #
- # Parameters:
- # id (required) - The ID of a project
- # url (required) - The hook URL
- # Example Request:
- # POST /projects/:id/hooks
+ desc 'Add hook to project' do
+ success Entities::ProjectHook
+ end
+ params do
+ use :project_hook_properties
+ end
post ":id/hooks" do
- required_attributes! [:url]
- attrs = attributes_for_keys [
- :url,
- :push_events,
- :issues_events,
- :merge_requests_events,
- :tag_push_events,
- :note_events,
- :build_events,
- :pipeline_events,
- :wiki_page_events,
- :enable_ssl_verification,
- :token
- ]
- @hook = user_project.hooks.new(attrs)
+ new_hook_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h
+ hook = user_project.hooks.new(new_hook_params)
- if @hook.save
- present @hook, with: Entities::ProjectHook
+ if hook.save
+ present hook, with: Entities::ProjectHook
else
- if @hook.errors[:url].present?
- error!("Invalid url given", 422)
- end
- not_found!("Project hook #{@hook.errors.messages}")
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
end
end
- # Update an existing project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # url (required) - The hook URL
- # Example Request:
- # PUT /projects/:id/hooks/:hook_id
+ desc 'Update an existing project hook' do
+ success Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: "The ID of the hook to update"
+ use :project_hook_properties
+ end
put ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- required_attributes! [:url]
- attrs = attributes_for_keys [
- :url,
- :push_events,
- :issues_events,
- :merge_requests_events,
- :tag_push_events,
- :note_events,
- :build_events,
- :pipeline_events,
- :wiki_page_events,
- :enable_ssl_verification,
- :token
- ]
+ hook = user_project.hooks.find(params[:hook_id])
+
+ new_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h
+ new_params.delete('hook_id')
- if @hook.update_attributes attrs
- present @hook, with: Entities::ProjectHook
+ if hook.update_attributes(new_params)
+ present hook, with: Entities::ProjectHook
else
- if @hook.errors[:url].present?
- error!("Invalid url given", 422)
- end
- not_found!("Project hook #{@hook.errors.messages}")
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
end
end
- # Deletes project hook. This is an idempotent function.
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of hook to delete
- # Example Request:
- # DELETE /projects/:id/hooks/:hook_id
+ desc 'Deletes project hook' do
+ success Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
+ end
delete ":id/hooks/:hook_id" do
- required_attributes! [:hook_id]
-
begin
- @hook = user_project.hooks.destroy(params[:hook_id])
+ present user_project.hooks.destroy(params[:hook_id]), with: Entities::ProjectHook
rescue
# ProjectHook can raise Error if hook_id not found
not_found!("Error deleting hook #{params[:hook_id]}")
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index da16e24d7ea..6b856128c2e 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -139,7 +139,8 @@ module API
:shared_runners_enabled,
:snippets_enabled,
:visibility_level,
- :wiki_enabled]
+ :wiki_enabled,
+ :only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved?
@@ -193,7 +194,8 @@ module API
:shared_runners_enabled,
:snippets_enabled,
:visibility_level,
- :wiki_enabled]
+ :wiki_enabled,
+ :only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved?
@@ -275,7 +277,8 @@ module API
:shared_runners_enabled,
:snippets_enabled,
:visibility_level,
- :wiki_enabled]
+ :wiki_enabled,
+ :only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs)
authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present?
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index c885fcd7ea3..c4cb1c7924a 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -17,12 +17,12 @@ module API
present current_settings, with: Entities::ApplicationSetting
end
- # Modify applicaiton settings
+ # Modify application settings
#
# Example Request:
# PUT /application/settings
put "application/settings" do
- attributes = current_settings.attributes.keys - ["id"]
+ attributes = ["repository_storage"] + current_settings.attributes.keys - ["id"]
attrs = attributes_for_keys(attributes)
if current_settings.update_attributes(attrs)
diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/environment_logger.rb
index 89ce7144b1b..407cc572656 100644
--- a/lib/gitlab/production_logger.rb
+++ b/lib/gitlab/environment_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
- class ProductionLogger < Gitlab::Logger
+ class EnvironmentLogger < Gitlab::Logger
def self.file_name_noext
- 'production'
+ Rails.env
end
end
end
diff --git a/lib/tasks/gitlab/generate_docs.rake b/lib/tasks/gitlab/generate_docs.rake
deleted file mode 100644
index f6448c38e10..00000000000
--- a/lib/tasks/gitlab/generate_docs.rake
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace :gitlab do
- desc "GitLab | Generate sdocs for project"
- task generate_docs: :environment do
- system(*%W(bundle exec sdoc -o doc/code app lib))
- end
-end
-
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 8c8bc1b0f1c..7f4298db59f 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -10,54 +10,38 @@ describe 'bin/changelog' do
expect(options.amend).to eq true
end
- it 'parses --force' do
- options = described_class.parse(%w[foo --force bar])
+ it 'parses --force and -f' do
+ %w[--force -f].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.force).to eq true
+ expect(options.force).to eq true
+ end
end
- it 'parses -f' do
- options = described_class.parse(%w[foo -f bar])
+ it 'parses --merge-request and -m' do
+ %w[--merge-request -m].each do |flag|
+ options = described_class.parse(%W[foo #{flag} 1234 bar])
- expect(options.force).to eq true
+ expect(options.merge_request).to eq 1234
+ end
end
- it 'parses --merge-request' do
- options = described_class.parse(%w[foo --merge-request 1234 bar])
+ it 'parses --dry-run and -n' do
+ %w[--dry-run -n].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.merge_request).to eq 1234
+ expect(options.dry_run).to eq true
+ end
end
- it 'parses -m' do
- options = described_class.parse(%w[foo -m 4321 bar])
-
- expect(options.merge_request).to eq 4321
- end
-
- it 'parses --dry-run' do
- options = described_class.parse(%w[foo --dry-run bar])
-
- expect(options.dry_run).to eq true
- end
-
- it 'parses -n' do
- options = described_class.parse(%w[foo -n bar])
-
- expect(options.dry_run).to eq true
- end
-
- it 'parses --git-username' do
+ it 'parses --git-username and -u' do
allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
- options = described_class.parse(%w[foo --git-username bar])
-
- expect(options.author).to eq 'Jane Doe'
- end
- it 'parses -u' do
- allow(described_class).to receive(:git_user_name).and_return('John Smith')
- options = described_class.parse(%w[foo -u bar])
+ %w[--git-username -u].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.author).to eq 'John Smith'
+ expect(options.author).to eq 'Jane Doe'
+ end
end
it 'parses -h' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 940d54f8686..49127aecc63 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -297,6 +297,72 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do
+ let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
+
+ context 'when enabled' do
+ before do
+ project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
+ end
+
+ context 'with unresolved discussion' do
+ before do
+ expect(merge_request).not_to be_discussions_resolved
+ end
+
+ it 'returns :failed' do
+ merge_with_sha
+
+ expect(assigns(:status)).to eq(:failed)
+ end
+ end
+
+ context 'with all discussions resolved' do
+ before do
+ merge_request.discussions.each { |d| d.resolve!(user) }
+ expect(merge_request).to be_discussions_resolved
+ end
+
+ it 'returns :success' do
+ merge_with_sha
+
+ expect(assigns(:status)).to eq(:success)
+ end
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
+ end
+
+ context 'with unresolved discussion' do
+ before do
+ expect(merge_request).not_to be_discussions_resolved
+ end
+
+ it 'returns :success' do
+ merge_with_sha
+
+ expect(assigns(:status)).to eq(:success)
+ end
+ end
+
+ context 'with all discussions resolved' do
+ before do
+ merge_request.discussions.each { |d| d.resolve!(user) }
+ expect(merge_request).to be_discussions_resolved
+ end
+
+ it 'returns :success' do
+ merge_with_sha
+
+ expect(assigns(:status)).to eq(:success)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index f780e01253c..37eb49c94df 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -68,6 +68,11 @@ FactoryGirl.define do
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
+ factory :merge_request_with_diff_notes do
+ after(:create) do |mr|
+ create(:diff_note_on_merge_request, noteable: mr, project: mr.source_project)
+ end
+ end
factory :labeled_merge_request do
transient do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 2f82fafc13a..d92c66b689d 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -7,15 +7,16 @@ describe "Admin Runners" do
describe "Runners page" do
before do
- runner = FactoryGirl.create(:ci_runner)
+ runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
pipeline = FactoryGirl.create(:ci_pipeline)
FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
visit admin_runners_path
end
- it { page.has_text? "Manage Runners" }
- it { page.has_text? "To register a new runner" }
- it { page.has_text? "Runners with last contact less than a minute ago: 1" }
+ it 'has all necessary texts' do
+ expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "Runners with last contact less than a minute ago: 1"
+ end
describe 'search' do
before do
@@ -27,8 +28,10 @@ describe "Admin Runners" do
search_form.click_button 'Search'
end
- it { expect(page).to have_content("runner-foo") }
- it { expect(page).not_to have_content("runner-bar") }
+ it 'shows correct runner' do
+ expect(page).to have_content("runner-foo")
+ expect(page).not_to have_content("runner-bar")
+ end
end
end
@@ -46,8 +49,10 @@ describe "Admin Runners" do
end
describe 'projects' do
- it { expect(page).to have_content(@project1.name_with_namespace) }
- it { expect(page).to have_content(@project2.name_with_namespace) }
+ it 'contains project names' do
+ expect(page).to have_content(@project1.name_with_namespace)
+ expect(page).to have_content(@project2.name_with_namespace)
+ end
end
describe 'search' do
@@ -57,8 +62,10 @@ describe "Admin Runners" do
search_form.click_button 'Search'
end
- it { expect(page).to have_content(@project1.name_with_namespace) }
- it { expect(page).not_to have_content(@project2.name_with_namespace) }
+ it 'contains name of correct project' do
+ expect(page).to have_content(@project1.name_with_namespace)
+ expect(page).not_to have_content(@project2.name_with_namespace)
+ end
end
describe 'enable/create' do
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 4dd9548cfc5..21ee6cedbae 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -19,6 +19,17 @@ describe "Dashboard Issues Feed", feature: true do
expect(body).to have_selector('title', text: "#{user.name} issues")
end
+ it "renders atom feed with url parameters" do
+ visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+
+ link = find('link[type="application/atom+xml"]')
+ params = CGI::parse(URI.parse(link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('state' => ['opened'])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ end
+
context "issue with basic fields" do
let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') }
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 09c140868fb..863412d18eb 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -3,10 +3,14 @@ require 'spec_helper'
describe 'Issues Feed', feature: true do
describe 'GET /issues' do
let!(:user) { create(:user) }
+ let!(:group) { create(:group) }
let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, project: project) }
- before { project.team << [user, :developer] }
+ before do
+ project.team << [user, :developer]
+ group.add_developer(user)
+ end
context 'when authenticated' do
it 'renders atom feed' do
@@ -33,5 +37,28 @@ describe 'Issues Feed', feature: true do
expect(body).to have_selector('entry summary', text: issue.title)
end
end
+
+ it "renders atom feed with url parameters for project issues" do
+ visit namespace_project_issues_path(project.namespace, project,
+ :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+
+ link = find('link[type="application/atom+xml"]')
+ params = CGI::parse(URI.parse(link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('state' => ['opened'])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ end
+
+ it "renders atom feed with url parameters for group issues" do
+ visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+
+ link = find('link[type="application/atom+xml"]')
+ params = CGI::parse(URI.parse(link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('state' => ['opened'])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5910803df51..44646ffc602 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -12,11 +12,15 @@ describe 'Commits' do
end
let!(:pipeline) do
- FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha
+ create(:ci_pipeline,
+ project: project,
+ ref: project.default_branch,
+ sha: project.commit.sha,
+ status: :success)
end
context 'commit status is Generic Commit Status' do
- let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
+ let!(:status) { create(:generic_commit_status, pipeline: pipeline) }
before do
project.team << [@user, :reporter]
@@ -39,7 +43,7 @@ describe 'Commits' do
end
context 'commit status is Ci Build' do
- let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline }
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
@@ -48,13 +52,22 @@ describe 'Commits' do
end
describe 'Project commits' do
+ let!(:pipeline_from_other_branch) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'fix',
+ sha: project.commit.sha,
+ status: :failed)
+ end
+
before do
visit namespace_project_commits_path(project.namespace, project, :master)
end
- it 'shows build status' do
+ it 'shows correct build status from default branch' do
page.within("//li[@id='commit-#{pipeline.short_sha}']") do
- expect(page).to have_css(".ci-status-link")
+ expect(page).to have_css('.ci-status-link')
+ expect(page).to have_css('.ci-status-icon-success')
end
end
end
@@ -64,9 +77,11 @@ describe 'Commits' do
visit ci_status_path(pipeline)
end
- it { expect(page).to have_content pipeline.sha[0..7] }
- it { expect(page).to have_content pipeline.git_commit_message }
- it { expect(page).to have_content pipeline.git_author_name }
+ it 'shows pipeline`s data' do
+ expect(page).to have_content pipeline.sha[0..7]
+ expect(page).to have_content pipeline.git_commit_message
+ expect(page).to have_content pipeline.git_author_name
+ end
end
context 'Download artifacts' do
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index 9b54b5301e5..b898f9bc64f 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -44,6 +44,22 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_selector('.issue', count: 1)
end
+
+ it 'updates atom feed link' do
+ visit_issues(milestone_title: '', assignee_id: user.id)
+
+ link = find('.nav-controls a', text: 'Subscribe')
+ params = CGI::parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
+ auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('milestone_title' => [''])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('milestone_title' => [''])
+ expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
+ end
end
def show_milestone_dropdown
@@ -51,7 +67,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_selector('.dropdown-content', visible: true)
end
- def visit_issues
- visit issues_dashboard_path
+ def visit_issues(*args)
+ visit issues_dashboard_path(*args)
end
end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 78208aed46d..2798db92f0f 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -4,6 +4,7 @@ describe 'Filter issues', feature: true do
include WaitForAjax
let!(:project) { create(:project) }
+ let!(:group) { create(:group) }
let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
@@ -11,6 +12,7 @@ describe 'Filter issues', feature: true do
before do
project.team << [user, :master]
+ group.add_developer(user)
login_as(user)
create(:issue, project: project)
end
@@ -347,4 +349,36 @@ describe 'Filter issues', feature: true do
end
end
end
+
+ it 'updates atom feed link for project issues' do
+ visit namespace_project_issues_path(project.namespace, project, milestone_title: '', assignee_id: user.id)
+
+ link = find('.nav-controls a', text: 'Subscribe')
+ params = CGI::parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
+ auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('milestone_title' => [''])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('milestone_title' => [''])
+ expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
+ end
+
+ it 'updates atom feed link for group issues' do
+ visit issues_group_path(group, milestone_title: '', assignee_id: user.id)
+
+ link = find('.nav-controls a', text: 'Subscribe')
+ params = CGI::parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
+ auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('milestone_title' => [''])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('milestone_title' => [''])
+ expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
+ end
end
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb
new file mode 100644
index 00000000000..7f11db3c417
--- /dev/null
+++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+feature 'Check if mergeable with unresolved discussions', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
+
+ before do
+ login_as user
+ project.team << [user, :master]
+ end
+
+ context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
+ before do
+ project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
+ end
+
+ context 'with unresolved discussions' do
+ it 'does not allow to merge' do
+ visit_merge_request(merge_request)
+
+ expect(page).not_to have_button 'Accept Merge Request'
+ expect(page).to have_content('This merge request has unresolved discussions')
+ end
+ end
+
+ context 'with all discussions resolved' do
+ before do
+ merge_request.discussions.each { |d| d.resolve!(user) }
+ end
+
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+ end
+
+ context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
+ before do
+ project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
+ end
+
+ context 'with unresolved discussions' do
+ it 'does not allow to merge' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+
+ context 'with all discussions resolved' do
+ before do
+ merge_request.discussions.each { |d| d.resolve!(user) }
+ end
+
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+ end
+
+ def visit_merge_request(merge_request)
+ visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ end
+end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index c77e719c5df..c46bd8d449f 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Edit Merge Request', feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
before do
project.team << [user, :master]
@@ -28,5 +28,17 @@ feature 'Edit Merge Request', feature: true do
expect(page).to have_content 'Someone edited the merge request the same time you did'
end
+
+ it 'allows to unselect "Remove source branch"' do
+ merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
+ expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
+
+ visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ uncheck 'Remove source branch when merge request is accepted'
+
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Remove source branch'
+ end
end
end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 63a23a14f20..a8022a5361f 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -79,12 +79,14 @@ describe "Builds" do
click_link "Cancel running"
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
- it { expect(page).to have_content 'canceled' }
- it { expect(page).to have_content @build.short_sha }
- it { expect(page).to have_content @build.ref }
- it { expect(page).to have_content @build.name }
- it { expect(page).not_to have_link 'Cancel running' }
+ it 'shows all necessary content' do
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_content 'canceled'
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ expect(page).not_to have_link 'Cancel running'
+ end
end
describe "GET /:project/builds/:id" do
@@ -93,10 +95,12 @@ describe "Builds" do
visit namespace_project_build_path(@project.namespace, @project, @build)
end
- it { expect(page.status_code).to eq(200) }
- it { expect(page).to have_content @commit.sha[0..7] }
- it { expect(page).to have_content @commit.git_commit_message }
- it { expect(page).to have_content @commit.git_author_name }
+ it 'shows commit`s data' do
+ expect(page.status_code).to eq(200)
+ expect(page).to have_content @commit.sha[0..7]
+ expect(page).to have_content @commit.git_commit_message
+ expect(page).to have_content @commit.git_author_name
+ end
end
context "Build from other project" do
@@ -167,7 +171,7 @@ describe "Builds" do
describe 'Variables' do
before do
- @trigger_request = create :ci_trigger_request_with_variables
+ @trigger_request = create :ci_trigger_request_with_variables
@build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request
visit namespace_project_build_path(@project.namespace, @project, @build)
end
@@ -176,14 +180,14 @@ describe "Builds" do
expect(page).to have_css('.reveal-variables')
expect(page).not_to have_css('.js-build-variable')
expect(page).not_to have_css('.js-build-value')
-
+
click_button 'Reveal Variables'
expect(page).not_to have_css('.reveal-variables')
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
end
- end
+ end
end
describe "POST /:project/builds/:id/cancel" do
@@ -194,9 +198,11 @@ describe "Builds" do
click_link "Cancel"
end
- it { expect(page.status_code).to eq(200) }
- it { expect(page).to have_content 'canceled' }
- it { expect(page).to have_content 'Retry' }
+ it 'loads the page and shows all needed controls' do
+ expect(page.status_code).to eq(200)
+ expect(page).to have_content 'canceled'
+ expect(page).to have_content 'Retry'
+ end
end
context "Build from other project" do
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index e796ee570b7..09aa6758b5c 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -183,4 +183,19 @@ describe 'Edit Project Settings', feature: true do
end
end
end
+
+ # Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/24056
+ describe 'project statistic visibility' do
+ let!(:project) { create(:project, :private) }
+
+ before do
+ project.team << [member, :guest]
+ login_as(member)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it "does not show project statistic for guest" do
+ expect(page).not_to have_selector('.project-stats')
+ end
+ end
end
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index b9e66243d84..d1f2bc78884 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -36,17 +36,54 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(page).not_to have_content project_2.name_with_namespace
end
- it 'filters by author' do
- click_button 'Author'
- within '.dropdown-menu-author' do
- fill_in 'Search authors', with: user_1.name
- click_link user_1.name
+ context "Author filter" do
+ it 'filters by author' do
+ click_button 'Author'
+
+ within '.dropdown-menu-author' do
+ fill_in 'Search authors', with: user_1.name
+ click_link user_1.name
+ end
+
+ wait_for_ajax
+
+ expect(find('.todos-list')).to have_content user_1.name
+ expect(find('.todos-list')).not_to have_content user_2.name
end
- wait_for_ajax
+ it "shows only authors of existing todos" do
+ click_button 'Author'
+
+ within '.dropdown-menu-author' do
+ # It should contain two users + "Any Author"
+ expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
+ expect(page).to have_content(user_1.name)
+ expect(page).to have_content(user_2.name)
+ end
+ end
- expect(find('.todos-list')).to have_content user_1.name
- expect(find('.todos-list')).not_to have_content user_2.name
+ it "shows only authors of existing done todos" do
+ user_3 = create :user
+ user_4 = create :user
+ create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
+ create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done)
+
+ project_1.team << [user_3, :developer]
+ project_2.team << [user_4, :developer]
+
+ visit dashboard_todos_path(state: 'done')
+
+ click_button 'Author'
+
+ within '.dropdown-menu-author' do
+ # It should contain two users + "Any Author"
+ expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
+ expect(page).to have_content(user_3.name)
+ expect(page).to have_content(user_4.name)
+ expect(page).not_to have_content(user_1.name)
+ expect(page).not_to have_content(user_2.name)
+ end
+ end
end
it 'filters by type' do
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index 1e2072f370a..49dfeab61d8 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable */
/*= require merge_request_widget */
-/*= require lib/utils/jquery.timeago.js */
+/*= require jquery.timeago.js */
(function() {
describe('MergeRequestWidget', function() {
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index cc215d252f9..2b76e056f3c 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -41,14 +41,62 @@ describe ApplicationSetting, models: true do
subject { setting }
end
- context 'repository storages inclussion' do
+ # Upgraded databases will have this sort of content
+ context 'repository_storages is a String, not an Array' do
+ before { setting.__send__(:raw_write_attribute, :repository_storages, 'default') }
+
+ it { expect(setting.repository_storages_before_type_cast).to eq('default') }
+ it { expect(setting.repository_storages).to eq(['default']) }
+ end
+
+ context 'repository storages' do
before do
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ storages = {
+ 'custom1' => 'tmp/tests/custom_repositories_1',
+ 'custom2' => 'tmp/tests/custom_repositories_2',
+ 'custom3' => 'tmp/tests/custom_repositories_3',
+
+ }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
- it { is_expected.to allow_value('custom').for(:repository_storage) }
- it { is_expected.not_to allow_value('alternative').for(:repository_storage) }
+ describe 'inclusion' do
+ it { is_expected.to allow_value('custom1').for(:repository_storages) }
+ it { is_expected.to allow_value(['custom2', 'custom3']).for(:repository_storages) }
+ it { is_expected.not_to allow_value('alternative').for(:repository_storages) }
+ it { is_expected.not_to allow_value(['alternative', 'custom1']).for(:repository_storages) }
+ end
+
+ describe 'presence' do
+ it { is_expected.not_to allow_value([]).for(:repository_storages) }
+ it { is_expected.not_to allow_value("").for(:repository_storages) }
+ it { is_expected.not_to allow_value(nil).for(:repository_storages) }
+ end
+
+ describe '.pick_repository_storage' do
+ it 'uses Array#sample to pick a random storage' do
+ array = double('array', sample: 'random')
+ expect(setting).to receive(:repository_storages).and_return(array)
+
+ expect(setting.pick_repository_storage).to eq('random')
+ end
+
+ describe '#repository_storage' do
+ it 'returns the first storage' do
+ setting.repository_storages = ['good', 'bad']
+
+ expect(setting.repository_storage).to eq('good')
+ end
+ end
+
+ describe '#repository_storage=' do
+ it 'overwrites repository_storages' do
+ setting.repository_storage = 'overwritten'
+
+ expect(setting.repository_storages).to eq(['overwritten'])
+ end
+ end
+ end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 51be3f36135..e3bb3482d67 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -205,12 +205,53 @@ eos
end
end
- describe '#ci_commits' do
- # TODO: kamil
- end
-
describe '#status' do
- # TODO: kamil
+ context 'without arguments for compound status' do
+ shared_examples 'giving the status from pipeline' do
+ it do
+ expect(commit.status).to eq(Ci::Pipeline.status)
+ end
+ end
+
+ context 'with pipelines' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline, project: project, sha: commit.sha)
+ end
+
+ it_behaves_like 'giving the status from pipeline'
+ end
+
+ context 'without pipelines' do
+ it_behaves_like 'giving the status from pipeline'
+ end
+ end
+
+ context 'when a particular ref is specified' do
+ let!(:pipeline_from_master) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ ref: 'master',
+ status: 'failed')
+ end
+
+ let!(:pipeline_from_fix) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ ref: 'fix',
+ status: 'success')
+ end
+
+ it 'gives pipelines from a particular branch' do
+ expect(commit.status('master')).to eq(pipeline_from_master.status)
+ expect(commit.status('fix')).to eq(pipeline_from_fix.status)
+ end
+
+ it 'gives compound status if ref is nil' do
+ expect(commit.status(nil)).to eq(commit.status)
+ end
+ end
end
describe '#participants' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 1067ff7bb4d..fb032a89d50 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -825,11 +825,8 @@ describe MergeRequest, models: true do
end
context 'when failed' do
- before { allow(subject).to receive(:broken?) { false } }
-
- context 'when project settings restrict to merge only if build succeeds and build failed' do
+ context 'when #mergeable_ci_state? is false' do
before do
- project.only_allow_merge_if_build_succeeds = true
allow(subject).to receive(:mergeable_ci_state?) { false }
end
@@ -837,6 +834,16 @@ describe MergeRequest, models: true do
expect(subject.mergeable_state?).to be_falsey
end
end
+
+ context 'when #mergeable_discussions_state? is false' do
+ before do
+ allow(subject).to receive(:mergeable_discussions_state?) { false }
+ end
+
+ it 'returns false' do
+ expect(subject.mergeable_state?).to be_falsey
+ end
+ end
end
end
@@ -887,7 +894,49 @@ describe MergeRequest, models: true do
end
end
- describe '#environments' do
+ describe '#mergeable_discussions_state?' do
+ let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
+
+ context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
+ let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
+
+ context 'with all discussions resolved' do
+ before do
+ merge_request.discussions.each { |d| d.resolve!(merge_request.author) }
+ end
+
+ it 'returns true' do
+ expect(merge_request.mergeable_discussions_state?).to be_truthy
+ end
+ end
+
+ context 'with unresolved discussions' do
+ before do
+ merge_request.discussions.each(&:unresolve!)
+ end
+
+ it 'returns false' do
+ expect(merge_request.mergeable_discussions_state?).to be_falsey
+ end
+ end
+ end
+
+ context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
+ let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: false) }
+
+ context 'with unresolved discussions' do
+ before do
+ merge_request.discussions.each(&:unresolve!)
+ end
+
+ it 'returns true' do
+ expect(merge_request.mergeable_discussions_state?).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe "#environments" do
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index aef277357cf..0245897938c 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -837,16 +837,19 @@ describe Project, models: true do
context 'repository storage by default' do
let(:project) { create(:empty_project) }
- subject { project.repository_storage }
-
before do
- storages = { 'alternative_storage' => '/some/path' }
+ storages = {
+ 'default' => 'tmp/tests/repositories',
+ 'picked' => 'tmp/tests/repositories',
+ }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
- stub_application_setting(repository_storage: 'alternative_storage')
- allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(true)
end
- it { is_expected.to eq('alternative_storage') }
+ it 'picks storage from ApplicationSetting' do
+ expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')
+
+ expect(project.repository_storage).to eq('picked')
+ end
end
context 'shared runners by default' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d1ed774a914..ba47479a2e1 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -256,6 +256,20 @@ describe User, models: true do
expect(users_without_two_factor).not_to include(user_with_2fa.id)
end
end
+
+ describe '.todo_authors' do
+ it 'filters users' do
+ create :user
+ user_2 = create :user
+ user_3 = create :user
+ current_user = create :user
+ create(:todo, user: current_user, author: user_2, state: :done)
+ create(:todo, user: current_user, author: user_3, state: :pending)
+
+ expect(User.todo_authors(current_user.id, 'pending')).to eq [user_3]
+ expect(User.todo_authors(current_user.id, 'done')).to eq [user_2]
+ end
+ end
end
describe "Respond to" do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3ba257256a0..7b47bf5afc1 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -37,7 +37,7 @@ describe API::API, api: true do
end
end
- context "when authenticated as admin" do
+ context "when authenticated as admin" do
it "admin: returns an array of all groups" do
get api("/groups", admin)
expect(response).to have_http_status(200)
@@ -55,6 +55,17 @@ describe API::API, api: true do
expect(json_response.length).to eq(1)
end
end
+
+ context "when using all_available in request" do
+ it "returns all groups you have access to" do
+ public_group = create :group, :public
+ get api("/groups", user1), all_available: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(public_group.name)
+ end
+ end
end
describe "GET /groups/:id" do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 46641fcd846..f702dfaaf53 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -82,7 +82,20 @@ describe API::API, api: true do
expect(json_response['message']['title']).to eq(['is invalid'])
end
- it 'returns 409 if label already exists' do
+ it 'returns 409 if label already exists in group' do
+ group = create(:group)
+ group_label = create(:group_label, group: group)
+ project.update(group: group)
+
+ post api("/projects/#{project.id}/labels", user),
+ name: group_label.name,
+ color: '#FFAABB'
+
+ expect(response).to have_http_status(409)
+ expect(json_response['message']).to eq('Label already exists')
+ end
+
+ it 'returns 409 if label already exists in project' do
post api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FFAABB'
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index 8f1e5ac9891..131c2d406ea 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -14,14 +14,14 @@ describe API::API, 'MergeRequestDiffs', api: true do
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
- context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) }
- let(:merge_request_diff) { merge_request.merge_request_diffs.first }
-
- it { expect(response.status).to eq 200 }
- it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) }
- it { expect(json_response.first['id']).to eq(merge_request_diff.id) }
- it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
+ it 'returns 200 for a valid merge request' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
+ merge_request_diff = merge_request.merge_request_diffs.first
+
+ expect(response.status).to eq 200
+ expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
+ expect(json_response.first['id']).to eq(merge_request_diff.id)
+ expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
end
it 'returns a 404 when merge_request_id not found' do
@@ -31,14 +31,14 @@ describe API::API, 'MergeRequestDiffs', api: true do
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
- context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) }
- let(:merge_request_diff) { merge_request.merge_request_diffs.first }
-
- it { expect(response.status).to eq 200 }
- it { expect(json_response['id']).to eq(merge_request_diff.id) }
- it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
- it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) }
+ it 'returns a 200 for a valid merge request' do
+ merge_request_diff = merge_request.merge_request_diffs.first
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
+
+ expect(response.status).to eq 200
+ expect(json_response['id']).to eq(merge_request_diff.id)
+ expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
+ expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size)
end
it 'returns a 404 when merge_request_id not found' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index b813ee967f8..bae4fa11ec2 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -186,14 +186,14 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
- context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
- let(:commit) { merge_request.commits.first }
-
- it { expect(response.status).to eq 200 }
- it { expect(json_response.size).to eq(merge_request.commits.size) }
- it { expect(json_response.first['id']).to eq(commit.id) }
- it { expect(json_response.first['title']).to eq(commit.title) }
+ it 'returns a 200 when merge request is valid' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
+ commit = merge_request.commits.first
+
+ expect(response.status).to eq 200
+ expect(json_response.size).to eq(merge_request.commits.size)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(json_response.first['title']).to eq(commit.title)
end
it 'returns a 404 when merge_request_id not found' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 973928d007a..3c8f0ac531a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -256,7 +256,8 @@ describe API::API, api: true do
merge_requests_enabled: false,
wiki_enabled: false,
only_allow_merge_if_build_succeeds: false,
- request_access_enabled: true
+ request_access_enabled: true,
+ only_allow_merge_if_all_discussions_are_resolved: false
})
post api('/projects', user), project
@@ -327,6 +328,22 @@ describe API::API, api: true do
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end
+ it 'sets a project as allowing merge even if discussions are unresolved' do
+ project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+
+ post api('/projects', user), project
+
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
+ end
+
+ it 'sets a project as allowing merge only if all discussions are resolved' do
+ project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+
+ post api('/projects', user), project
+
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
+ end
+
context 'when a visibility level is restricted' do
before do
@project = attributes_for(:project, { public: true })
@@ -448,6 +465,22 @@ describe API::API, api: true do
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end
+
+ it 'sets a project as allowing merge even if discussions are unresolved' do
+ project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
+ end
+
+ it 'sets a project as allowing merge only if all discussions are resolved' do
+ project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
+ end
end
describe "POST /projects/:id/uploads" do
@@ -509,6 +542,7 @@ describe API::API, api: true do
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
end
it 'returns a project by path name' do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index f4903d8e0be..096a8ebab70 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -33,6 +33,7 @@ describe API::API, 'Settings', api: true do
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['signin_enabled']).to be_falsey
expect(json_response['repository_storage']).to eq('custom')
+ expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['koding_enabled']).to be_truthy
expect(json_response['koding_url']).to eq('http://koding.example.com')
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 7b7d62feb2c..6d49c42c215 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -220,26 +220,33 @@ describe Ci::API::API do
end
context 'when request is valid' do
- it { expect(response.status).to eq 202 }
+ it 'gets correct response' do
+ expect(response.status).to eq 202
+ expect(response.header).to have_key 'Range'
+ expect(response.header).to have_key 'Build-Status'
+ end
+
it { expect(build.reload.trace).to eq 'BUILD TRACE appended' }
- it { expect(response.header).to have_key 'Range' }
- it { expect(response.header).to have_key 'Build-Status' }
end
context 'when content-range start is too big' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
- it { expect(response.status).to eq 416 }
- it { expect(response.header).to have_key 'Range' }
- it { expect(response.header['Range']).to eq '0-11' }
+ it 'gets correct response' do
+ expect(response.status).to eq 416
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
end
context 'when content-range start is too small' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
- it { expect(response.status).to eq 416 }
- it { expect(response.header).to have_key 'Range' }
- it { expect(response.header['Range']).to eq '0-11' }
+ it 'gets correct response' do
+ expect(response.status).to eq 416
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
end
context 'when Content-Range header is missing' do
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 43596f07cb5..d6c26fd8a94 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -109,10 +109,12 @@ describe Ci::API::API do
end
describe "DELETE /runners/delete" do
- let!(:runner) { FactoryGirl.create(:ci_runner) }
- before { delete ci_api("/runners/delete"), token: runner.token }
+ it 'returns 200' do
+ runner = FactoryGirl.create(:ci_runner)
+ delete ci_api("/runners/delete"), token: runner.token
- it { expect(response).to have_http_status 200 }
- it { expect(Ci::Runner.count).to eq(0) }
+ expect(response).to have_http_status 200
+ expect(Ci::Runner.count).to eq(0)
+ end
end
end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
new file mode 100644
index 00000000000..2734f5bedca
--- /dev/null
+++ b/spec/serializers/build_entity_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe BuildEntity do
+ let(:entity) do
+ described_class.new(build, request: double)
+ end
+
+ subject { entity.as_json }
+
+ context 'when build is a regular job' do
+ let(:build) { create(:ci_build) }
+
+ it 'contains url to build page and retry action' do
+ expect(subject).to include(:build_url, :retry_url)
+ expect(subject).not_to include(:play_url)
+ end
+
+ it 'does not contain sensitive information' do
+ expect(subject).not_to include(/token/)
+ expect(subject).not_to include(/variables/)
+ end
+ end
+
+ context 'when build is a manual action' do
+ let(:build) { create(:ci_build, :manual) }
+
+ it 'contains url to play action' do
+ expect(subject).to include(:play_url)
+ end
+ end
+end
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
new file mode 100644
index 00000000000..628e35c9a28
--- /dev/null
+++ b/spec/serializers/commit_entity_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe CommitEntity do
+ let(:entity) do
+ described_class.new(commit, request: request)
+ end
+
+ let(:request) { double('request') }
+ let(:project) { create(:project) }
+ let(:commit) { project.commit }
+
+ subject { entity.as_json }
+
+ before do
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ context 'when commit author is a user' do
+ before do
+ create(:user, email: commit.author_email)
+ end
+
+ it 'contains information about user' do
+ expect(subject.fetch(:author)).not_to be_nil
+ end
+ end
+
+ context 'when commit author is not a user' do
+ it 'does not contain author details' do
+ expect(subject.fetch(:author)).to be_nil
+ end
+ end
+
+ it 'contains commit URL' do
+ expect(subject).to include(:commit_url)
+ end
+
+ it 'needs to receive project in the request' do
+ expect(request).to receive(:project)
+ .and_return(project)
+
+ subject
+ end
+end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
new file mode 100644
index 00000000000..51b6de91571
--- /dev/null
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe DeploymentEntity do
+ let(:entity) do
+ described_class.new(deployment, request: double)
+ end
+
+ let(:deployment) { create(:deployment) }
+
+ subject { entity.as_json }
+
+ it 'exposes internal deployment id' do
+ expect(subject).to include(:iid)
+ end
+
+ it 'exposes nested information about branch' do
+ expect(subject[:ref][:name]).to eq 'master'
+ expect(subject[:ref][:ref_url]).not_to be_empty
+ end
+end
diff --git a/spec/serializers/entity_request_spec.rb b/spec/serializers/entity_request_spec.rb
new file mode 100644
index 00000000000..86654adfd54
--- /dev/null
+++ b/spec/serializers/entity_request_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe EntityRequest do
+ subject do
+ described_class.new(user: 'user', project: 'some project')
+ end
+
+ describe 'methods created' do
+ it 'defines accessible attributes' do
+ expect(subject.user).to eq 'user'
+ expect(subject.project).to eq 'some project'
+ end
+
+ it 'raises error when attribute is not defined' do
+ expect { subject.some_method }.to raise_error NoMethodError
+ end
+ end
+end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
new file mode 100644
index 00000000000..4ca8c299147
--- /dev/null
+++ b/spec/serializers/environment_entity_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe EnvironmentEntity do
+ let(:entity) do
+ described_class.new(environment, request: double)
+ end
+
+ let(:environment) { create(:environment) }
+ subject { entity.as_json }
+
+ it 'exposes latest deployment' do
+ expect(subject).to include(:last_deployment)
+ end
+
+ it 'exposes core elements of environment' do
+ expect(subject).to include(:id, :name, :state, :environment_url)
+ end
+end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
new file mode 100644
index 00000000000..37bc086826c
--- /dev/null
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe EnvironmentSerializer do
+ let(:serializer) do
+ described_class
+ .new(user: user, project: project)
+ .represent(resource)
+ end
+
+ let(:json) { serializer.as_json }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ context 'when there is a single object provided' do
+ before do
+ create(:ci_build, :manual, name: 'manual1',
+ pipeline: deployable.pipeline)
+ end
+
+ let(:deployment) do
+ create(:deployment, deployable: deployable,
+ user: user,
+ project: project,
+ sha: project.commit.id)
+ end
+
+ let(:deployable) { create(:ci_build) }
+ let(:resource) { deployment.environment }
+
+ it 'it generates payload for single object' do
+ expect(json).to be_an_instance_of Hash
+ end
+
+ it 'contains important elements of environment' do
+ expect(json)
+ .to include(:name, :external_url, :environment_url, :last_deployment)
+ end
+
+ it 'contains relevant information about last deployment' do
+ last_deployment = json.fetch(:last_deployment)
+
+ expect(last_deployment)
+ .to include(:ref, :user, :commit, :deployable, :manual_actions)
+ end
+ end
+
+ context 'when there is a collection of objects provided' do
+ let(:project) { create(:empty_project) }
+ let(:resource) { create_list(:environment, 2) }
+
+ it 'contains important elements of environment' do
+ expect(json.first)
+ .to include(:last_deployment, :name, :external_url)
+ end
+
+ it 'generates payload for collection' do
+ expect(json).to be_an_instance_of Array
+ end
+ end
+end
diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
new file mode 100644
index 00000000000..c5d11cbcf5e
--- /dev/null
+++ b/spec/serializers/user_entity_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe UserEntity do
+ let(:entity) { described_class.new(user) }
+ let(:user) { create(:user) }
+ subject { entity.as_json }
+
+ it 'exposes user name and login' do
+ expect(subject).to include(:username, :name)
+ end
+
+ it 'does not expose passwords' do
+ expect(subject).not_to include(/password/)
+ end
+
+ it 'does not expose tokens' do
+ expect(subject).not_to include(/token/)
+ end
+
+ it 'does not expose 2FA OTPs' do
+ expect(subject).not_to include(/otp/)
+ end
+end
diff --git a/app/assets/javascripts/lib/utils/jquery.timeago.js b/vendor/assets/javascripts/jquery.timeago.js
index de76cdd2ea7..de76cdd2ea7 100644
--- a/app/assets/javascripts/lib/utils/jquery.timeago.js
+++ b/vendor/assets/javascripts/jquery.timeago.js