diff options
33 files changed, 217 insertions, 74 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 043c6a11c4f..e43afbb4cc9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -89,6 +89,14 @@ // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; + // `hashchange` is not triggered when link target is already in window.location + $body.on('click', 'a[href^="#"]', function() { + var href = this.getAttribute('href'); + if (href.substr(1) === gl.utils.getLocationHash()) { + setTimeout(gl.utils.handleLocationHash, 1); + } + }); + // prevent default action for disabled buttons $('.btn').click(function(e) { if ($(this).hasClass('disabled')) { 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/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 index 88a19fc6e1d..5852b8bbdb7 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -59,9 +59,11 @@ }, methods: { updateTooltip: function () { - $(this.$refs.button) - .tooltip('hide') - .tooltip('fixTitle'); + this.$nextTick(() => { + $(this.$refs.button) + .tooltip('hide') + .tooltip('fixTitle'); + }); }, resolve: function () { if (!this.canResolve) return; @@ -90,7 +92,7 @@ new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); } - this.$nextTick(this.updateTooltip); + this.updateTooltip(); }); } }, 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/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 3857bbb743b..87c579ac757 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -77,7 +77,7 @@ var _a, _y, regexp, match, atSymbolsWithBar, atSymbolsWithoutBar; atSymbolsWithBar = Object.keys(this.app.controllers).join('|'); atSymbolsWithoutBar = Object.keys(this.app.controllers).join(''); - subtext = subtext.split(' ').pop(); + subtext = subtext.split(/\s+/g).pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); 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/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3e1fded6b6b..b0c4a6edf57 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -489,9 +489,9 @@ $project-network-controls-color: #888; */ $runner-state-shared-bg: #32b186; $runner-state-specific-bg: #3498db; -$runner-status-online-color: green; -$runner-status-offline-color: gray; -$runner-status-paused-color: red; +$runner-status-online-color: $green-normal; +$runner-status-offline-color: $gray-darkest; +$runner-status-paused-color: $red-normal; /* Stat Graph diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 9478b66f536..6b4d1f85564 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -1,52 +1,3 @@ -// Limit MR description for side-by-side diff view -.container-limited { - &.limit-container-width { - .detail-page-header { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - - .issuable-details { - .detail-page-description, - .mr-source-target, - .mr-state-widget, - .merge-manually { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - - .merge-request-tabs-holder { - &.affix { - border-bottom: 1px solid $border-color; - - .nav-links { - border: 0; - } - } - - .container-fluid { - padding-left: 0; - padding-right: 0; - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - } - } - - .diffs { - .mr-version-controls, - .files-changed { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - } - } -} - .issuable-details { section { .issuable-discussion { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 41b1b47713d..4cb53ab6fce 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -424,6 +424,10 @@ .merge-request-tabs-holder { background-color: $white-light; + .container-limited { + max-width: $limited-layout-width; + } + &.affix { top: 100px; left: 0; 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/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/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml b/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml new file mode 100644 index 00000000000..c31c89dc4bc --- /dev/null +++ b/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml @@ -0,0 +1,4 @@ +--- +title: ensure permalinks scroll to correct position on multiple clicks +merge_request: 8046 +author: diff --git a/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml b/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml new file mode 100644 index 00000000000..e60c42cdfba --- /dev/null +++ b/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml @@ -0,0 +1,4 @@ +--- +title: Change status colors of runners to better defaults +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/gfm-new-line-fix.yml b/changelogs/unreleased/gfm-new-line-fix.yml new file mode 100644 index 00000000000..751bb356b5f --- /dev/null +++ b/changelogs/unreleased/gfm-new-line-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fixed GFM dropdown not showing on new lines +merge_request: +author: 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/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 3489331a1b6..82c9bd0e6e6 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -47,6 +47,18 @@ feature 'GFM autocomplete', feature: true, js: true do expect_to_wrap(true, label_item, note, label.title) end + it "shows dropdown after a new line" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('test') + note.native.send_keys(:enter) + note.native.send_keys(:enter) + note.native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + it "does not show dropdown when preceded with a special character" do note = find('#note_note') page.within '.timeline-content-form' do |