summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--PROCESS.md3
-rw-r--r--app/assets/javascripts/boards/models/issue.js.es62
-rw-r--r--app/assets/javascripts/boards/models/label.js.es62
-rw-r--r--app/assets/javascripts/boards/models/list.js.es62
-rw-r--r--app/assets/javascripts/boards/models/milestone.js.es62
-rw-r--r--app/assets/javascripts/boards/models/user.js.es62
-rw-r--r--app/assets/javascripts/boards/services/board_service.js.es64
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/models/note.js.es62
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js.es62
-rw-r--r--app/assets/javascripts/preview_markdown.js115
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es62
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb3
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/snippets_controller.rb3
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb2
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_conflicts.html.haml30
-rw-r--r--changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml4
-rw-r--r--changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml4
-rw-r--r--changelogs/unreleased/26126-cache-even-when-no-projects.yml4
-rw-r--r--changelogs/unreleased/dz-improve-admin-group-routing.yml4
-rw-r--r--changelogs/unreleased/fix-api-deprecation.yml4
-rw-r--r--changelogs/unreleased/fix-light-hr-in-descriptions.yml4
-rw-r--r--changelogs/unreleased/fix-mentioned-issue-text-grammar.yml4
-rw-r--r--config/routes/admin.rb2
-rw-r--r--doc/development/sidekiq_debugging.md13
-rw-r--r--doc/development/ux_guide/animation.md17
-rw-r--r--doc/development/ux_guide/basics.md14
-rw-r--r--doc/development/ux_guide/img/animation-autoscroll.gifbin0 -> 302217 bytes
-rw-r--r--doc/development/ux_guide/img/animation-reorder.gifbin0 -> 70515 bytes
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/notes.rb2
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb37
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb30
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb22
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb22
-rw-r--r--spec/features/admin/admin_groups_spec.rb10
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb4
-rw-r--r--spec/requests/api/helpers_spec.rb4
-rw-r--r--spec/routing/admin_routing_spec.rb10
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb37
49 files changed, 338 insertions, 114 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4b2f041ab5..1baf5b3257c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ entry.
## 8.15.2 (2016-12-27)
+- Fix finding the latest pipeline. !8301
- Fix mr list timestamp alignment. !8271
- Fix discussion overlap text in regular screens. !8273
- Fixes mini-pipeline-graph dropdown animation and stage position in chrome, firefox and safari. !8282
diff --git a/Gemfile b/Gemfile
index fff808e9805..eadb67405ee 100644
--- a/Gemfile
+++ b/Gemfile
@@ -347,5 +347,5 @@ gem 'paranoia', '~> 2.2'
gem 'health_check', '~> 2.2.0'
# System information
-gem 'vmstat', '~> 2.2'
+gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 765d57c6238..1c192d2fc6e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -773,7 +773,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
- vmstat (2.2.0)
+ vmstat (2.3.0)
warden (1.2.6)
rack (>= 1.0)
web-console (2.3.0)
@@ -982,7 +982,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
- vmstat (~> 2.2)
+ vmstat (~> 2.3.0)
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
diff --git a/PROCESS.md b/PROCESS.md
index 8af660fbdd1..cbeb781cd3c 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -69,7 +69,8 @@ to add details to the issue.
- ~UX needs help from a UX designer
- ~Frontend needs help from a Front-end engineer. Please follow the
["Implement design & UI elements" guidelines].
-- ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
+- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
+ encourage people to contribute to. Not exclusive with other labels.
- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6
index 1199e022ff1..cd942c8332b 100644
--- a/app/assets/javascripts/boards/models/issue.js.es6
+++ b/app/assets/javascripts/boards/models/issue.js.es6
@@ -71,3 +71,5 @@ class ListIssue {
return Vue.http.patch(url, data);
}
}
+
+window.ListIssue = ListIssue;
diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js.es6
index 8f20a1bbec7..9af88d167d6 100644
--- a/app/assets/javascripts/boards/models/label.js.es6
+++ b/app/assets/javascripts/boards/models/label.js.es6
@@ -10,3 +10,5 @@ class ListLabel {
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
}
}
+
+window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index a8d38c16485..fb13f529b11 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -148,3 +148,5 @@ class List {
});
}
}
+
+window.List = List;
diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js.es6
index 9c173c1b70b..c867b06d320 100644
--- a/app/assets/javascripts/boards/models/milestone.js.es6
+++ b/app/assets/javascripts/boards/models/milestone.js.es6
@@ -6,3 +6,5 @@ class ListMilestone {
this.title = obj.title;
}
}
+
+window.ListMilestone = ListMilestone;
diff --git a/app/assets/javascripts/boards/models/user.js.es6 b/app/assets/javascripts/boards/models/user.js.es6
index a8a3892e2ad..8e9de4d4cbb 100644
--- a/app/assets/javascripts/boards/models/user.js.es6
+++ b/app/assets/javascripts/boards/models/user.js.es6
@@ -8,3 +8,5 @@ class ListUser {
this.avatar = user.avatar_url;
}
}
+
+window.ListUser = ListUser;
diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6
index 189a8703198..f18633f0913 100644
--- a/app/assets/javascripts/boards/services/board_service.js.es6
+++ b/app/assets/javascripts/boards/services/board_service.js.es6
@@ -65,4 +65,6 @@ class BoardService {
issue
});
}
-};
+}
+
+window.BoardService = BoardService;
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6
index efcd46680a7..fa518ba4d33 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6
@@ -92,3 +92,5 @@ class DiscussionModel {
return false;
}
}
+
+window.DiscussionModel = DiscussionModel;
diff --git a/app/assets/javascripts/diff_notes/models/note.js.es6 b/app/assets/javascripts/diff_notes/models/note.js.es6
index e3bce1d2038..f3a7cba5ef6 100644
--- a/app/assets/javascripts/diff_notes/models/note.js.es6
+++ b/app/assets/javascripts/diff_notes/models/note.js.es6
@@ -9,3 +9,5 @@ class NoteModel {
this.resolved_by = resolved_by;
}
}
+
+window.NoteModel = NoteModel;
diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6
index 15ec7b76c3d..575a45d9802 100644
--- a/app/assets/javascripts/environments/services/environments_service.js.es6
+++ b/app/assets/javascripts/environments/services/environments_service.js.es6
@@ -20,3 +20,5 @@ class EnvironmentsService {
return this.environments.get();
}
}
+
+window.EnvironmentsService = EnvironmentsService;
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 1e261cd49c2..89f7e976934 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -1,14 +1,17 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, wrap-iife, no-else-return, consistent-return, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, camelcase, prefer-arrow-callback, max-len */
+/* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
// and showing a warning when more than `x` users are referenced.
//
-(function() {
- var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
+(function () {
+ var lastTextareaPreviewed;
+ var markdownPreview;
+ var previewButtonSelector;
+ var writeButtonSelector;
- window.MarkdownPreview = (function() {
+ window.MarkdownPreview = (function () {
function MarkdownPreview() {}
// Minimum number of users referenced before triggering a warning
@@ -16,73 +19,71 @@
MarkdownPreview.prototype.ajaxCache = {};
- MarkdownPreview.prototype.showPreview = function(form) {
- var mdText, preview;
- preview = form.find('.js-md-preview');
- mdText = form.find('textarea.markdown-area').val();
+ MarkdownPreview.prototype.showPreview = function ($form) {
+ var mdText;
+ var preview = $form.find('.js-md-preview');
+ if (preview.hasClass('md-preview-loading')) {
+ return;
+ }
+ mdText = $form.find('textarea.markdown-area').val();
+
if (mdText.trim().length === 0) {
preview.text('Nothing to preview.');
- return this.hideReferencedUsers(form);
+ this.hideReferencedUsers($form);
} else {
- preview.text('Loading...');
- return this.renderMarkdown(mdText, (function(_this) {
- return function(response) {
- preview.html(response.body);
- preview.renderGFM();
- return _this.renderReferencedUsers(response.references.users, form);
- };
- })(this));
+ preview.addClass('md-preview-loading').text('Loading...');
+ this.fetchMarkdownPreview(mdText, (function (response) {
+ preview.removeClass('md-preview-loading').html(response.body);
+ preview.renderGFM();
+ this.renderReferencedUsers(response.references.users, $form);
+ }).bind(this));
}
};
- MarkdownPreview.prototype.renderMarkdown = function(text, success) {
+ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, success) {
if (!window.preview_markdown_path) {
return;
}
if (text === this.ajaxCache.text) {
- return success(this.ajaxCache.response);
+ success(this.ajaxCache.response);
+ return;
}
- return $.ajax({
+ $.ajax({
type: 'POST',
url: window.preview_markdown_path,
data: {
text: text
},
dataType: 'json',
- success: (function(_this) {
- return function(response) {
- _this.ajaxCache = {
- text: text,
- response: response
- };
- return success(response);
+ success: (function (response) {
+ this.ajaxCache = {
+ text: text,
+ response: response
};
- })(this)
+ success(response);
+ }).bind(this)
});
};
- MarkdownPreview.prototype.hideReferencedUsers = function(form) {
- var referencedUsers;
- referencedUsers = form.find('.referenced-users');
- return referencedUsers.hide();
+ MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
+ $form.find('.referenced-users').hide();
};
- MarkdownPreview.prototype.renderReferencedUsers = function(users, form) {
+ MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
var referencedUsers;
- referencedUsers = form.find('.referenced-users');
+ referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) {
if (users.length >= this.referenceThreshold) {
referencedUsers.show();
- return referencedUsers.find('.js-referenced-users-count').text(users.length);
+ referencedUsers.find('.js-referenced-users-count').text(users.length);
} else {
- return referencedUsers.hide();
+ referencedUsers.hide();
}
}
};
return MarkdownPreview;
-
- })();
+ }());
markdownPreview = new window.MarkdownPreview();
@@ -92,19 +93,14 @@
lastTextareaPreviewed = null;
- $.fn.setupMarkdownPreview = function() {
- var $form, form_textarea;
- $form = $(this);
- form_textarea = $form.find('textarea.markdown-area');
- form_textarea.on('input', function() {
- return markdownPreview.hideReferencedUsers($form);
- });
- return form_textarea.on('blur', function() {
- return markdownPreview.showPreview($form);
+ $.fn.setupMarkdownPreview = function () {
+ var $form = $(this);
+ $form.find('textarea.markdown-area').on('input', function () {
+ markdownPreview.hideReferencedUsers($form);
});
};
- $(document).on('markdown-preview:show', function(e, $form) {
+ $(document).on('markdown-preview:show', function (e, $form) {
if (!$form) {
return;
}
@@ -115,10 +111,10 @@
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
- return markdownPreview.showPreview($form);
+ markdownPreview.showPreview($form);
});
- $(document).on('markdown-preview:hide', function(e, $form) {
+ $(document).on('markdown-preview:hide', function (e, $form) {
if (!$form) {
return;
}
@@ -129,34 +125,33 @@
// toggle content
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
- return $form.find('.md-preview-holder').hide();
+ $form.find('.md-preview-holder').hide();
});
- $(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
+ $(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
$(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
- return keyboardEvent.preventDefault();
+ keyboardEvent.preventDefault();
} else if (lastTextareaPreviewed) {
$target = lastTextareaPreviewed;
$(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
- return keyboardEvent.preventDefault();
+ keyboardEvent.preventDefault();
}
});
- $(document).on('click', previewButtonSelector, function(e) {
+ $(document).on('click', previewButtonSelector, function (e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
- return $(document).triggerHandler('markdown-preview:show', [$form]);
+ $(document).triggerHandler('markdown-preview:show', [$form]);
});
- $(document).on('click', writeButtonSelector, function(e) {
+ $(document).on('click', writeButtonSelector, function (e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
- return $(document).triggerHandler('markdown-preview:hide', [$form]);
+ $(document).triggerHandler('markdown-preview:hide', [$form]);
});
-
-}).call(this);
+}());
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
index 08264ad3d2f..03f4531abf5 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
@@ -76,3 +76,5 @@ class ProtectedBranchDropdown {
this.$dropdownFooter.toggleClass('hidden', !branchName);
}
}
+
+window.ProtectedBranchDropdown = ProtectedBranchDropdown;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 5e3a91af86e..34757c57acf 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -66,6 +66,7 @@ pre {
hr {
margin: $gl-padding 0;
+ border-top: 1px solid darken($gray-normal, 8%);
}
.str-truncated {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 237869aa544..d129eb12a45 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -98,7 +98,7 @@
}
.label {
- padding: 8px 9px 9px $gl-padding;
+ padding: 8px 9px 9px;
font-size: 14px;
}
}
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 61a3a03182a..add1c819adf 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -9,7 +9,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def show
- @group = Group.with_statistics.find_by_full_path(params[:id])
+ @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
@members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = AccessRequestsFinder.new(@group).execute(current_user)
@projects = @group.projects.with_statistics.page(params[:projects_page])
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index d425d0f9014..e3933e3d7b1 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -4,6 +4,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
+ if @todos.out_of_range? && @todos.total_pages != 0
+ redirect_to url_for(params.merge(page: @todos.total_pages))
+ end
end
def destroy
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 4f66e01e0f7..2beb0df8a07 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -25,6 +25,9 @@ class Projects::IssuesController < Projects::ApplicationController
def index
@issues = issues_collection
@issues = @issues.page(params[:page])
+ if @issues.out_of_range? && @issues.total_pages != 0
+ return redirect_to url_for(params.merge(page: @issues.total_pages))
+ end
if params[:label_name].present?
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 3abebdfd032..fc8a289d49d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -38,6 +38,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def index
@merge_requests = merge_requests_collection
@merge_requests = @merge_requests.page(params[:page])
+ if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
+ return redirect_to url_for(params.merge(page: @merge_requests.total_pages))
+ end
if params[:label_name].present?
labels_params = { project_id: @project.id, title: params[:label_name] }
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 0720be2e55d..02a97c1c574 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -26,6 +26,9 @@ class Projects::SnippetsController < Projects::ApplicationController
scope: params[:scope]
)
@snippets = @snippets.page(params[:page])
+ if @snippets.out_of_range? && @snippets.total_pages != 0
+ redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
+ end
end
def new
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 7d38ac3a374..8559908e0c3 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -74,7 +74,7 @@ module Users
# remove - The IDs of the authorization rows to remove.
# add - Rows to insert in the form `[user id, project id, access level]`
def update_authorizations(remove = [], add = [])
- return if remove.empty? && add.empty?
+ return if remove.empty? && add.empty? && user.authorized_projects_populated
User.transaction do
user.remove_project_authorizations(remove) unless remove.empty?
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index f4aa1609a1b..40fbac7025a 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -42,6 +42,6 @@
- if mr_issues_mentioned_but_not_closing.present?
#{"Issue".pluralize(mr_issues_mentioned_but_not_closing.size)}
!= markdown issues_sentence(mr_issues_mentioned_but_not_closing), pipeline: :gfm, author: @merge_request.author
- #{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not closed.
+ #{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not be closed.
diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
index af3096f04d9..c98b2c42597 100644
--- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -1,21 +1,23 @@
+- can_resolve = @merge_request.conflicts_can_be_resolved_by?(current_user)
+- can_resolve_in_ui = @merge_request.conflicts_can_be_resolved_in_ui?
+- can_merge = @merge_request.can_be_merged_via_command_line_by?(current_user)
+
%h4.has-conflicts
= icon("exclamation-triangle")
This merge request contains merge conflicts
%p
- Please
- - if @merge_request.conflicts_can_be_resolved_by?(current_user)
- - if @merge_request.conflicts_can_be_resolved_in_ui?
- = link_to "resolve these conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- - else
- %span.has-tooltip{title: "These conflicts cannot be resolved through GitLab"}
- resolve these conflicts locally
- - else
- resolve these conflicts
-
+ To merge this request, resolve these conflicts
+ - if can_resolve && !can_resolve_in_ui
+ locally
or
+ - unless can_merge
+ ask someone with write access to this repository to
+ merge it locally.
- - if @merge_request.can_be_merged_via_command_line_by?(current_user)
- #{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- - else
- ask someone with write access to this repository to merge this request manually.
+- if (can_resolve && can_resolve_in_ui) || can_merge
+ .btn-group
+ - if can_resolve && can_resolve_in_ui
+ = link_to "Resolve conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn"
+ - if can_merge
+ = link_to "Merge locally", "#modal_merge_info", class: "btn how_to_merge_link vlink", "data-toggle" => "modal"
diff --git a/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml b/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml
new file mode 100644
index 00000000000..5570ede4a9a
--- /dev/null
+++ b/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent empty pagination when list is not empty
+merge_request: 8172
+author:
diff --git a/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml b/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml
new file mode 100644
index 00000000000..574c322803c
--- /dev/null
+++ b/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml
@@ -0,0 +1,4 @@
+---
+title: Improve visibility of "Resolve conflicts" and "Merge locally" actions
+merge_request: 8229
+author:
diff --git a/changelogs/unreleased/26126-cache-even-when-no-projects.yml b/changelogs/unreleased/26126-cache-even-when-no-projects.yml
new file mode 100644
index 00000000000..53e14ac9edf
--- /dev/null
+++ b/changelogs/unreleased/26126-cache-even-when-no-projects.yml
@@ -0,0 +1,4 @@
+---
+title: Cache project authorizations even when user has access to zero projects
+merge_request: 8327
+author:
diff --git a/changelogs/unreleased/dz-improve-admin-group-routing.yml b/changelogs/unreleased/dz-improve-admin-group-routing.yml
new file mode 100644
index 00000000000..2360b965c90
--- /dev/null
+++ b/changelogs/unreleased/dz-improve-admin-group-routing.yml
@@ -0,0 +1,4 @@
+---
+title: Fix 500 error when visit group from admin area if group name contains dot
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-api-deprecation.yml b/changelogs/unreleased/fix-api-deprecation.yml
new file mode 100644
index 00000000000..90285ddf058
--- /dev/null
+++ b/changelogs/unreleased/fix-api-deprecation.yml
@@ -0,0 +1,4 @@
+---
+title: Fix a Grape deprecation, use `#request_method` instead of `#route_method`
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-light-hr-in-descriptions.yml b/changelogs/unreleased/fix-light-hr-in-descriptions.yml
new file mode 100644
index 00000000000..8efd471e416
--- /dev/null
+++ b/changelogs/unreleased/fix-light-hr-in-descriptions.yml
@@ -0,0 +1,4 @@
+---
+title: Darkened hr border color in descriptions because of update of bootstrap
+merge_request: 8333
+author:
diff --git a/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml b/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml
new file mode 100644
index 00000000000..1d001e6b568
--- /dev/null
+++ b/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml
@@ -0,0 +1,4 @@
+---
+title: Fix a minor grammar error in merge request widget
+merge_request: 8337
+author:
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 0dd2c8f7aef..23295b43c92 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -32,7 +32,7 @@ namespace :admin do
scope(path: 'groups/*id',
controller: :groups,
- constraints: { id: Gitlab::Regex.namespace_route_regex }) do
+ constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
scope(as: :group) do
put :members_update
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
index cea11e5f126..d6d770e27c1 100644
--- a/doc/development/sidekiq_debugging.md
+++ b/doc/development/sidekiq_debugging.md
@@ -3,12 +3,15 @@
## Log arguments to Sidekiq jobs
If you want to see what arguments are being passed to Sidekiq jobs you can set
-the SIDEKIQ_LOG_ARGUMENTS environment variable.
+the `SIDEKIQ_LOG_ARGUMENTS` [environment variable]
+(https://docs.gitlab.com/omnibus/settings/environment-variables.html) to `1` (true).
+
+Example:
```
-SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
+gitlab_rails['env'] = {"SIDEKIQ_LOG_ARGUMENTS" => "1"}
```
-It is not recommend to enable this setting in production because some Sidekiq
-jobs (such as sending a password reset email) take secret arguments (for
-example the password reset token).
+Please note: It is not recommend to enable this setting in production because some
+Sidekiq jobs (such as sending a password reset email) take secret arguments (for
+example the password reset token). \ No newline at end of file
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index daeb15460c2..903e54bf9dc 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -39,4 +39,19 @@ When information is updating in place, a quick, subtle animation is needed. The
![Quick update animation](img/animation-quickupdate.gif)
-> TODO: Add guidance for other kinds of animation \ No newline at end of file
+### Moving transitions
+
+When elements move on screen, there should be a quick animation so it is clear to users what moved where. The timing of this animation differs based on the amount of movement and change. Consider animations between `200ms` and `400ms`.
+
+#### Shifting elements on reorder
+An example of a moving transition is when elements have to move out of the way when you drag an element.
+
+View the [interactive example](http://codepen.io/awhildy/full/ALyKPE/) here.
+
+![Reorder animation](img/animation-reorder.gif)
+
+#### Autoscroll the page
+Another example of a moving transition is when you have to autoscroll the page to keep an active element visible.
+
+View the [interactive example](http://codepen.io/awhildy/full/PbxgVo/) here.
+![Autoscroll animation](img/animation-autoscroll.gif) \ No newline at end of file
diff --git a/doc/development/ux_guide/basics.md b/doc/development/ux_guide/basics.md
index e81729556d8..76ec7fd466b 100644
--- a/doc/development/ux_guide/basics.md
+++ b/doc/development/ux_guide/basics.md
@@ -50,13 +50,13 @@ GitLab uses Font Awesome icons throughout our interface.
## Color
-| | |
-| :------: | :------- |
-| ![Blue](img/color-blue.png) | Blue is used to highlight primary active elements (such as the current tab), as well as other organizational and managing commands.|
-| ![Green](img/color-green.png) | Green is for actions that create new objects. |
-| ![Orange](img/color-orange.png) | Orange is used for warnings |
-| ![Red](img/color-red.png) | Red is reserved for delete and other destructive commands |
-| ![Grey](img/color-grey.png) | Grey is used for neutral secondary elements. Depending on context, white is sometimes used instead. |
+| | State | Action |
+| :------: | :------- | :------- |
+| ![Blue](img/color-blue.png) | Primary and active (such as the current tab) | Organizational, managing, and retry commands|
+| ![Green](img/color-green.png) | Opened | Create new objects |
+| ![Orange](img/color-orange.png) | Warning | Non destructive action |
+| ![Red](img/color-red.png) | Closed | Delete and other destructive commands |
+| ![Grey](img/color-grey.png) | Neutral | Neutral secondary commands |
> TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage.
diff --git a/doc/development/ux_guide/img/animation-autoscroll.gif b/doc/development/ux_guide/img/animation-autoscroll.gif
new file mode 100644
index 00000000000..155b0234c64
--- /dev/null
+++ b/doc/development/ux_guide/img/animation-autoscroll.gif
Binary files differ
diff --git a/doc/development/ux_guide/img/animation-reorder.gif b/doc/development/ux_guide/img/animation-reorder.gif
new file mode 100644
index 00000000000..ccdeb3d396f
--- /dev/null
+++ b/doc/development/ux_guide/img/animation-reorder.gif
Binary files differ
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index fe00c83bff3..ee9247ee240 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -96,7 +96,7 @@ module API
end
def authenticate_non_get!
- authenticate! unless %w[GET HEAD].include?(route.route_method)
+ authenticate! unless %w[GET HEAD].include?(route.request_method)
end
def authenticate_by_gitlab_shell_token!
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index d0faf17714b..284e4cf549a 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -69,8 +69,6 @@ module API
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_str}/:noteable_id/notes" do
- required_attributes! [:body]
-
opts = {
note: params[:body],
noteable_type: noteables_str.classify,
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
new file mode 100644
index 00000000000..288984cfba9
--- /dev/null
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Dashboard::TodosController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:todo_service) { TodoService.new }
+
+ describe 'GET #index' do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ context 'when using pagination' do
+ let(:last_page) { user.todos.page().total_pages }
+ let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
+
+ before do
+ issues.each { |issue| todo_service.new_issue(issue, user) }
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ end
+
+ it 'redirects to last_page if page number is larger than number of pages' do
+ get :index, page: (last_page + 1).to_param
+
+ expect(response).to redirect_to(dashboard_todos_path(page: last_page))
+ end
+
+ it 'redirects to correspondent page' do
+ get :index, page: last_page
+
+ expect(assigns(:todos).current_page).to eq(last_page)
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index dbe5ddccbcf..e2321f2034b 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -52,6 +52,36 @@ describe Projects::IssuesController do
expect(response).to have_http_status(404)
end
end
+
+ context 'with page param' do
+ let(:last_page) { project.issues.page().total_pages }
+ let!(:issue_list) { create_list(:issue, 2, project: project) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ end
+
+ it 'redirects to last_page if page number is larger than number of pages' do
+ get :index,
+ namespace_id: project.namespace.path.to_param,
+ project_id: project.path.to_param,
+ page: (last_page + 1).to_param
+
+ expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
+ end
+
+ it 'redirects to specified page' do
+ get :index,
+ namespace_id: project.namespace.path.to_param,
+ project_id: project.path.to_param,
+ page: last_page.to_param
+
+ expect(assigns(:issues).current_page).to eq(last_page)
+ expect(response).to have_http_status(200)
+ end
+ end
end
describe 'GET #new' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 440b897ddc6..2a411d78395 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -127,11 +127,29 @@ describe Projects::MergeRequestsController do
end
describe 'GET index' do
- def get_merge_requests
+ def get_merge_requests(page = nil)
get :index,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
- state: 'opened'
+ state: 'opened', page: page.to_param
+ end
+
+ context 'when page param' do
+ let(:last_page) { project.merge_requests.page().total_pages }
+ let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
+ it 'redirects to last_page if page number is larger than number of pages' do
+ get_merge_requests(last_page + 1)
+
+ expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
+ end
+
+ it 'redirects to specified page' do
+ get_merge_requests(last_page)
+
+ expect(assigns(:merge_requests).current_page).to eq(last_page)
+ expect(response).to have_http_status(200)
+ end
end
context 'when filtering by opened state' do
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 72a3ebf2ebd..32b0e42c3cd 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -11,6 +11,28 @@ describe Projects::SnippetsController do
end
describe 'GET #index' do
+ context 'when page param' do
+ let(:last_page) { project.snippets.page().total_pages }
+ let!(:project_snippet) { create(:project_snippet, :public, project: project, author: user) }
+
+ it 'redirects to last_page if page number is larger than number of pages' do
+ get :index,
+ namespace_id: project.namespace.path,
+ project_id: project.path, page: (last_page + 1).to_param
+
+ expect(response).to redirect_to(namespace_project_snippets_path(page: last_page))
+ end
+
+ it 'redirects to specified page' do
+ get :index,
+ namespace_id: project.namespace.path,
+ project_id: project.path, page: last_page.to_param
+
+ expect(assigns(:snippets).current_page).to eq(last_page)
+ expect(response).to have_http_status(200)
+ end
+ end
+
context 'when the project snippet is private' do
let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 0aa01fc499a..9c19db6b420 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -17,6 +17,16 @@ feature 'Admin Groups', feature: true do
end
end
+ describe 'show a group' do
+ scenario 'shows the group' do
+ group = create(:group, :private)
+
+ visit admin_group_path(group)
+
+ expect(page).to have_content("Group: #{group.name}")
+ end
+ end
+
describe 'group edit' do
scenario 'shows the visibility level radio populated with the group visibility_level value' do
group = create(:group, :private)
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index dc32c8f7373..c73065cdce1 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -41,7 +41,7 @@ feature 'Merge Request closing issues message', feature: true do
let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not closed.")
+ expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
end
end
@@ -49,7 +49,7 @@ feature 'Merge Request closing issues message', feature: true do
let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not closed.")
+ expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index c3d7ac3eef8..b8ee2293a33 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -396,7 +396,7 @@ describe API::Helpers, api: true do
%w[HEAD GET].each do |method_name|
context "method is #{method_name}" do
before do
- expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
+ expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'does not raise an error' do
@@ -410,7 +410,7 @@ describe API::Helpers, api: true do
%w[POST PUT PATCH DELETE].each do |method_name|
context "method is #{method_name}" do
before do
- expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
+ expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'calls authenticate!' do
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 661b671301e..99c44bde151 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -122,12 +122,18 @@ describe Admin::HealthCheckController, "routing" do
end
describe Admin::GroupsController, "routing" do
+ let(:name) { 'complex.group-namegit' }
+
it "to #index" do
expect(get("/admin/groups")).to route_to('admin/groups#index')
end
it "to #show" do
- expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab')
- expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup')
+ expect(get("/admin/groups/#{name}")).to route_to('admin/groups#show', id: name)
+ expect(get("/admin/groups/#{name}/subgroup")).to route_to('admin/groups#show', id: "#{name}/subgroup")
+ end
+
+ it "to #edit" do
+ expect(get("/admin/groups/#{name}/edit")).to route_to('admin/groups#edit', id: name)
end
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 72c8f7cd8ec..1f6919151de 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -54,12 +54,37 @@ describe Users::RefreshAuthorizedProjectsService do
end
describe '#update_authorizations' do
- it 'does nothing when there are no rows to add and remove' do
- expect(user).not_to receive(:remove_project_authorizations)
- expect(ProjectAuthorization).not_to receive(:insert_authorizations)
- expect(user).not_to receive(:set_authorized_projects_column)
+ context 'when there are no rows to add and remove' do
+ it 'does not change authorizations' do
+ expect(user).not_to receive(:remove_project_authorizations)
+ expect(ProjectAuthorization).not_to receive(:insert_authorizations)
- service.update_authorizations([], [])
+ service.update_authorizations([], [])
+ end
+
+ context 'when the authorized projects column is not set' do
+ before do
+ user.update!(authorized_projects_populated: nil)
+ end
+
+ it 'populates the authorized projects column' do
+ service.update_authorizations([], [])
+
+ expect(user.authorized_projects_populated).to eq true
+ end
+ end
+
+ context 'when the authorized projects column is set' do
+ before do
+ user.update!(authorized_projects_populated: true)
+ end
+
+ it 'does nothing' do
+ expect(user).not_to receive(:set_authorized_projects_column)
+
+ service.update_authorizations([], [])
+ end
+ end
end
it 'removes authorizations that should be removed' do
@@ -84,7 +109,7 @@ describe Users::RefreshAuthorizedProjectsService do
it 'populates the authorized projects column' do
# make sure we start with a nil value no matter what the default in the
# factory may be.
- user.update(authorized_projects_populated: nil)
+ user.update!(authorized_projects_populated: nil)
service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])