diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2016-08-20 00:09:58 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2016-08-20 00:09:58 +0800 |
commit | a49151f0d186e5abd45b5d0bb2ed4947a55858a2 (patch) | |
tree | 1a4e2576f964af346f62929bef94313909a6471e | |
parent | f8496a33b33fb266eb43d3926432d6349bf1086a (diff) | |
parent | 1f127006085eb61c6f5381d196ad156d15ff470d (diff) | |
download | gitlab-ce-a49151f0d186e5abd45b5d0bb2ed4947a55858a2.tar.gz |
Merge remote-tracking branch 'upstream/master' into wall-clock-time-for-showing-pipeline
* upstream/master: (50 commits)
Increased vertical alignment of labels for issues in lists
Changed file name Updated spec HAML
Changed tests to use JS tests
Addressed feedback
Made logic simpler by moving away from underscorejs
Added tooltip to label value in collapsed sidebar
Add play icon SVG
Have hover color of builds span full width
Fix alignment of icon on commits page
Change sleep to wait_for_ajax
Added tests
Destroy branch delete tooltip when row is removed
Move and improvement comment in pipeline fixtures
Fix notification_service argument error of declined invitation emails
Update contribution acceptance criteria with tests requirements
Fixed keyboard shortcuts not working on issue boards
Hides tooltip when dragging Fixes issue with cursor not changing when dragging
Hides tooltips when dragging issues
Add a spec testing a second side effect of `Repository#merge`.
drop execute bit
...
59 files changed, 624 insertions, 157 deletions
diff --git a/CHANGELOG b/CHANGELOG index e9d08c5bb23..62f1953b720 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) + - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) @@ -18,13 +19,16 @@ v 8.11.0 (unreleased) - API: Endpoints for enabling and disabling deploy keys - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833 - Use long options for curl examples in documentation !5703 (winniehell) + - Added tooltip listing label names to the labels value in the collapsed issuable sidebar - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) + - Fix badge count alignment (ClemMakesApps) - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Allow naming U2F devices !5833 - Ignore URLs starting with // in Markdown links !5677 (winniehell) - Fix CI status icon link underline (ClemMakesApps) - The Repository class is now instrumented + - Fix commit mention font inconsistency (ClemMakesApps) - Fix filter label tooltip HTML rendering (ClemMakesApps) - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Expand commit message width in repo view (ClemMakesApps) @@ -56,6 +60,7 @@ v 8.11.0 (unreleased) - Enforce 2FA restrictions on API authentication endpoints !5820 - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs + - Fix branch title trailing space on hover (ClemMakesApps) - Clean up unused routes (Josef Strzibny) - Fix issue on empty project to allow developers to only push to protected branches if given permission - API: Add enpoints for pipelines @@ -72,6 +77,7 @@ v 8.11.0 (unreleased) - Fix devise deprecation warnings. - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - Update version_sorter and use new interface for faster tag sorting + - Load branches asynchronously in Cherry Pick and Revert dialogs. - Optimize checking if a user has read access to a list of issues !5370 - Store all DB secrets in secrets.yml, under descriptive names !5274 - Fix syntax highlighting in file editor @@ -106,12 +112,14 @@ v 8.11.0 (unreleased) - Fix search for notes which belongs to deleted objects - Allow Akismet to be trained by submitting issues as spam or ham !5538 - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) + - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Allow branch names ending with .json for graph and network page !5579 (winniehell) - Add the `sprockets-es6` gem - Improve OAuth2 client documentation (muteor) - Fix diff comments inverted toggle bug (ClemMakesApps) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed + - Fix button missing type (ClemMakesApps) - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Add commit stats in commit api. !5517 (dixpac) @@ -120,6 +128,7 @@ v 8.11.0 (unreleased) - edit_blob_link will use blob passed onto the options parameter - Make error pages responsive (Takuya Noguchi) - The performance of the project dropdown used for moving issues has been improved + - Move to project dropdown with infinite scroll for better performance - Fix skip_repo parameter being ignored when destroying a namespace - Add all builds into stage/job dropdowns on builds page - Change requests_profiles resource constraint to catch virtually any file @@ -128,6 +137,7 @@ v 8.11.0 (unreleased) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix bug where destroying a namespace would not always destroy projects - Fix RequestProfiler::Middleware error when code is reloaded in development + - Allow horizontal scrolling of code blocks in issue body - Catch what warden might throw when profiling requests to re-throw it - Avoid commit lookup on diff_helper passing existing local variable to the helper method - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) @@ -142,6 +152,7 @@ v 8.11.0 (unreleased) - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) - Add pipelines tab to merge requests + - Fix notification_service argument error of declined invitation emails - Fix a memory leak caused by Banzai::Filter::SanitizationFilter - Speed up todos queries by limiting the projects set we join with - Ensure file editing in UI does not overwrite commited changes without warning user diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbc8e15bebf..d8093a61b4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format. 1. The change is as small as possible 1. Include proper tests and make all tests pass (unless it contains a test - exposing a bug in existing code) + exposing a bug in existing code). Every new class should have corresponding + unit tests, even if the class is exercised at a higher level, such as a feature test. 1. If you suspect a failing CI build is unrelated to your contribution, you may try and restart the failing CI job or ask a developer to fix the aforementioned failing test diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a122fa2d637..c77983896a3 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -153,7 +153,9 @@ }); }); $('.remove-row').bind('ajax:success', function() { - return $(this).closest('li').fadeOut(); + $(this).tooltip('destroy') + .closest('li') + .fadeOut(); }); $('.js-remove-tr').bind('ajax:before', function() { return $(this).hide(); diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index e17784e7948..5ef6a1b2277 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -55,7 +55,7 @@ draggable: '.is-draggable', handle: '.js-board-handle', onEnd: (e) => { - document.body.classList.remove('is-dragging'); + gl.issueBoards.onEnd(); if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { const order = this.sortable.toArray(), diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 1503d14c508..dceacb25452 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -63,6 +63,8 @@ Store.moving.issue = card.issue; Store.moving.list = card.list; + + gl.issueBoards.onStart(); }, onAdd: (e) => { gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index b7afe4897b6..8e8d13ede5a 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -2,6 +2,17 @@ window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; + gl.issueBoards.onStart = () => { + $('.has-tooltip').tooltip('hide') + .tooltip('disable'); + document.body.classList.add('is-dragging'); + }; + + gl.issueBoards.onEnd = () => { + $('.has-tooltip').tooltip('enable'); + document.body.classList.remove('is-dragging'); + }; + gl.issueBoards.getBoardSortableDefaultOptions = (obj) => { let defaultSortOptions = { forceFallback: true, @@ -11,12 +22,8 @@ filter: '.has-tooltip', scrollSensitivity: 100, scrollSpeed: 20, - onStart () { - document.body.classList.add('is-dragging'); - }, - onEnd () { - document.body.classList.remove('is-dragging'); - } + onStart: gl.issueBoards.onStart, + onEnd: gl.issueBoards.onEnd } Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js index 75f8b730195..75f8b730195 100755..100644 --- a/app/assets/javascripts/boards/test_utils/simulate_drag.js +++ b/app/assets/javascripts/boards/test_utils/simulate_drag.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 74c4ab563f9..32e3aa62358 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -20,6 +20,9 @@ path = page.split(':'); shortcut_handler = null; switch (page) { + case 'projects:boards:show': + shortcut_handler = new ShortcutsNavigation(); + break; case 'projects:issues:index': Issuable.init(); new IssuableBulkActions(); diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 297d4f029f0..b7f92ae9883 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -102,20 +102,34 @@ }; IssuableForm.prototype.initMoveDropdown = function() { - var $moveDropdown; + var $moveDropdown, pageSize; $moveDropdown = $('.js-move-dropdown'); if ($moveDropdown.length) { + pageSize = $moveDropdown.data('page-size'); return $('.js-move-dropdown').select2({ ajax: { url: $moveDropdown.data('projects-url'), - results: function(data) { + quietMillis: 125, + data: function(term, page, context) { return { - results: data + search: term, + offset_id: context }; }, - data: function(query) { + results: function(data) { + var context, + more; + + if (data.length >= pageSize) + more = true; + + if (data[data.length - 1]) + context = data[data.length - 1].id; + return { - search: query + results: data, + more: more, + context: context }; } }, diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 0526430989f..565dbeacdb3 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,7 +4,7 @@ var _this; _this = this; $('.js-label-select').each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); labelUrl = $dropdown.data('labels'); @@ -21,6 +21,7 @@ $block = $selectbox.closest('.block'); $form = $dropdown.closest('form'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); + $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); if (issueUpdateURL != null) { @@ -31,7 +32,11 @@ labelNoneHTMLTemplate = '<span class="no-value">None</span>'; } - new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId); + $sidebarLabelTooltip.tooltip(); + + if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { + new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId); + } saveLabelData = function() { var data, selected; @@ -52,7 +57,7 @@ dataType: 'JSON', data: data }).done(function(data) { - var labelCount, template; + var labelCount, template, labelTooltipTitle, labelTitles; $loading.fadeOut(); $dropdown.trigger('loaded.gl.dropdown'); $selectbox.hide(); @@ -66,6 +71,27 @@ } $value.removeAttr('style').html(template); $sidebarCollapsedValue.text(labelCount); + + if (data.labels.length) { + labelTitles = data.labels.map(function(label) { + return label.title; + }); + + if (labelTitles.length > 5) { + labelTitles = labelTitles.slice(0, 5); + labelTitles.push('and ' + (data.labels.length - 5) + ' more'); + } + + labelTooltipTitle = labelTitles.join(', '); + } else { + labelTooltipTitle = ''; + $sidebarLabelTooltip.tooltip('destroy'); + } + + $sidebarLabelTooltip + .attr('title', labelTooltipTitle) + .tooltip('fixTitle'); + $('.has-tooltip', $value).tooltip({ container: 'body' }); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index b97f6d22715..4e1de4dfb72 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -65,7 +65,8 @@ url: $dropdown.data('refs-url'), data: { ref: $dropdown.data('ref') - } + }, + dataType: "json" }).done(function(refs) { return callback(refs); }); @@ -73,7 +74,7 @@ selectable: true, filterable: true, filterByText: true, - fieldName: 'ref', + fieldName: $dropdown.data('field-name'), renderRow: function(ref) { var link; if (ref.header != null) { diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index f1635a53763..7d3a063d6c2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -84,6 +84,15 @@ width: 100%; } } + + // Allows dynamic-width text in the dropdown toggle. + // Resizes to allow long text without overflowing the container. + &.dynamic { + width: auto; + min-width: 160px; + max-width: 100%; + padding-right: 25px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 407f1873431..d3e3fc50736 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -63,9 +63,10 @@ &.image_file { background: #eee; text-align: center; + img { - padding: 100px; - max-width: 50%; + padding: 20px; + max-width: 80%; } } diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index f4d35c4b4b1..c0de09f3968 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -2,7 +2,7 @@ * Styles that apply to all GFM related forms. */ -.gfm-commit, .gfm-commit_range { +.gfm-commit_range { font-family: $monospace_font; font-size: 90%; } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 26ad2870aa0..8374f30d0b2 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -1,6 +1,5 @@ .modal-body { position: relative; - overflow-y: auto; padding: 15px; .form-actions { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 7852fc9a424..9e924f99e9c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -72,6 +72,7 @@ font-weight: normal; background-color: #eee; color: #78a; + vertical-align: baseline; } } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 21d87cc9d34..b2e22b60440 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -45,7 +45,8 @@ min-width: 175px; } -.select2-results .select2-result-label { +.select2-results .select2-result-label, +.select2-more-results { padding: 10px 15px; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8659604cb8b..06874a993fa 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -14,12 +14,20 @@ margin-top: 0; } + // Single code lines should wrap code { font-family: $monospace_font; - white-space: pre; + white-space: pre-wrap; word-wrap: normal; } + // Multi-line code blocks should scroll horizontally + pre { + code { + white-space: pre; + } + } + kbd { display: inline-block; padding: 3px 5px; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index ad4b2d6496f..bea9ac75715 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -8,9 +8,13 @@ } .is-dragging { + // Important because plugin sets inline CSS + opacity: 1!important; + * { - cursor: -webkit-grabbing; - cursor: grabbing; + // !important to make sure no style can override this when dragging + cursor: -webkit-grabbing!important; + cursor: grabbing!important; } } @@ -254,11 +258,6 @@ opacity: 0.3; } -.is-dragging { - // Important because plugin sets inline CSS - opacity: 1!important; -} - .card { position: relative; width: 100%; @@ -316,6 +315,7 @@ .card-footer { margin-top: 5px; + line-height: 25px; .label { margin-right: 4px; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 81fce55853c..c1bb250b42d 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -168,7 +168,6 @@ text-overflow: ellipsis; &:hover { - background-color: $row-hover; color: $gl-text-color; } } @@ -190,6 +189,10 @@ display: block; } } + + &:hover { + background-color: $row-hover; + } } } } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index bbe0c6c5f1f..53ec0002afe 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -66,6 +66,15 @@ margin-left: 8px; } } + + .ci-status-link { + + svg { + position: relative; + top: 2px; + margin: 0 2px 0 3px; + } + } } .ci-status-link { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 1b389d83525..4d9c73c6840 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -34,11 +34,4 @@ } } } - - .wiki { - code { - white-space: pre-wrap; - word-break: keep-all; - } - } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index b4636269518..fcdaf671538 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -374,3 +374,10 @@ } } } + +.merge-request-details { + + .title { + margin-bottom: 20px; + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ce1c424624f..6fa097e3bf1 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -300,6 +300,17 @@ &.playable { background-color: $gray-light; + + svg { + height: 12px; + width: 12px; + position: relative; + top: 1px; + + path { + fill: $layout-link-gray; + } + } } .build-content { @@ -319,10 +330,6 @@ margin-right: 5px; } - .fa { - font-size: 13px; - } - // Connect first build in each stage with right horizontal line &:first-child { &::after { diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 48fe81b0d74..2de8ada3e29 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController diverging_commit_counts = repository.diverging_commit_counts(branch) [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max end + + respond_to do |format| + format.html + format.json do + render json: @repository.branch_names + end + end end def recent diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb index 3334b8556df..79eb45568be 100644 --- a/app/finders/move_to_project_finder.rb +++ b/app/finders/move_to_project_finder.rb @@ -1,4 +1,6 @@ class MoveToProjectFinder + PAGE_SIZE = 50 + def initialize(user) @user = user end @@ -8,6 +10,10 @@ class MoveToProjectFinder projects = projects.search(search) if search.present? projects = projects.excluding_project(from_project) + # infinite scroll using offset + projects = projects.where('projects.id < ?', offset_id) if offset_id.present? + projects = projects.limit(PAGE_SIZE) + # to ask for Project#name_with_namespace projects.includes(namespace: :owner) end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 94df7d131ca..bb285a17baf 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -39,7 +39,7 @@ module CiStatusHelper when 'running' 'icon_status_running' when 'play' - return icon('play fw') + 'icon_play' when 'created' 'icon_status_pending' else diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 47d174361db..b9baeb1d6c4 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -72,6 +72,15 @@ module IssuablesHelper end end + def issuable_labels_tooltip(labels, limit: 5) + first, last = labels.partition.with_index{ |_, i| i < limit } + + label_names = first.collect(&:name) + label_names << "and #{last.size} more" unless last.empty? + + label_names.join(', ') + end + private def sidebar_gutter_collapsed? diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 66a838b3d13..6139ed56e25 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -242,7 +242,6 @@ class NotificationService project_member.real_source_type, project_member.project.id, project_member.invite_email, - project_member.access_level, project_member.created_by_id ).deliver_later end @@ -269,7 +268,6 @@ class NotificationService group_member.real_source_type, group_member.group.id, group_member.invite_email, - group_member.access_level, group_member.created_by_id ).deliver_later end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 1d3b8fc3683..f7012595a5a 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -65,7 +65,7 @@ Graphs - if project_nav_tab? :issues - = nav_link(controller: [:issues, :labels, :milestones]) do + = nav_link(controller: [:issues, :labels, :milestones, :boards]) do = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do %span Issues diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4bd85061240..6192ccb710b 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -5,8 +5,8 @@ - number_commits_ahead = diverging_commit_counts[:ahead] %li(class="js-branch-#{branch.name}") %div - = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do - %span.item-title.str-truncated= branch.name + = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do + = branch.name - if branch.name == @repository.root_ref %span.label.label-primary default diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index d9b800a4ded..e4cd55b9f7a 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -17,7 +17,9 @@ .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 - = select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch" + = hidden_field_tag :target_branch, @project.default_branch, id: 'target_branch' + = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false }}) + - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index f733d49224e..29d767e7769 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -56,7 +56,8 @@ = pluralize(@commit.pipelines.count, 'pipeline') = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do = ci_icon_for_status(@commit.status) - = ci_label_for_status(@commit.status) + %span.ci-status-label + = ci_label_for_status(@commit.status) in = time_interval_in_words @commit.pipelines.total_duration diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9f1a046ea74..3fb4191c60e 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -22,7 +22,7 @@ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) .issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } %span.caret Options .dropdown-menu.dropdown-menu-align-right.hidden-lg diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index b24bdf22ceb..098ce19da21 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -14,7 +14,7 @@ - if can?(current_user, :update_merge_request, @merge_request) .issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } %span.caret Options .dropdown-menu.dropdown-menu-align-right.hidden-lg diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 835398b6f98..33d5cbff420 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -1,18 +1,20 @@ +- @no_container = true - page_title "Edit", @tag.name, "Tags" = render "projects/commits/head" -.row-content-block - .oneline - .title - Release notes for tag - %strong #{@tag.name} +%div{ class: container_class } + .sub-header-block.no-bottom-space + .oneline + .title + Release notes for tag + %strong #{@tag.name} + -.prepend-top-default = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' .error-alert - .form-actions.prepend-top-default + .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index ea7162d4d63..9a8252ab087 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -6,7 +6,7 @@ - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" } .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } = dropdown_title "Switch branch/tag" = dropdown_filter "Search branches and tags" diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg new file mode 100644 index 00000000000..80a6d41dbf6 --- /dev/null +++ b/app/views/shared/icons/_icon_play.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg>
\ No newline at end of file diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index d717c3d92ee..544ed6203aa 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -121,7 +121,7 @@ = label_tag :move_to_project_id, 'Move', class: 'control-label' .col-sm-10 .issuable-form-select-holder - = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) } + = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE } %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default', title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 8e2fcbdfab8..c1b50e65af5 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -109,7 +109,7 @@ - if issuable.project.labels.any? .block.labels - .sidebar-collapsed-icon + .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } = icon('tags') %span = issuable.labels_array.size diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_pipelines.rb index 069d9dd6226..49e6e2361b1 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -1,4 +1,4 @@ -class Gitlab::Seeder::Builds +class Gitlab::Seeder::Pipelines STAGES = %w[build test deploy notify] BUILDS = [ { name: 'build:linux', stage: 'build', status: :success }, @@ -7,11 +7,12 @@ class Gitlab::Seeder::Builds { name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success }, - { name: 'spinach:linux', stage: 'test', status: :pending }, - { name: 'spinach:osx', stage: 'test', status: :canceled }, - { name: 'cucumber:linux', stage: 'test', status: :running }, - { name: 'cucumber:osx', stage: 'test', status: :failed }, - { name: 'staging', stage: 'deploy', environment: 'staging', status: :success }, + { name: 'spinach:linux', stage: 'test', status: :success }, + { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, + { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending }, + { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, + { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, + { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] @@ -34,72 +35,86 @@ class Gitlab::Seeder::Builds end end + private + def pipelines - master_pipelines + merge_request_pipelines + create_master_pipelines + create_merge_request_pipelines end - def master_pipelines - create_pipelines_for(@project, 'master') + def create_master_pipelines + @project.repository.commits('master', limit: 4).map do |commit| + create_pipeline!(@project, 'master', commit) + end rescue [] end - def merge_request_pipelines - @project.merge_requests.last(5).map do |merge_request| - create_pipelines(merge_request.source_project, merge_request.source_branch, merge_request.commits.last(5)) - end.flatten + def create_merge_request_pipelines + pipelines = @project.merge_requests.first(3).map do |merge_request| + project = merge_request.source_project + branch = merge_request.source_branch + + merge_request.commits.last(4).map do |commit| + create_pipeline!(project, branch, commit) + end + end + + pipelines.flatten rescue [] end - def create_pipelines_for(project, ref) - commits = project.repository.commits(ref, limit: 5) - create_pipelines(project, ref, commits) + + def create_pipeline!(project, ref, commit) + project.pipelines.create(sha: commit.id, ref: ref) end - def create_pipelines(project, ref, commits) - commits.map do |commit| - project.pipelines.create(sha: commit.id, ref: ref) + def build_create!(pipeline, opts = {}) + attributes = job_attributes(pipeline, opts) + .merge(commands: '$ build command') + + Ci::Build.create!(attributes).tap do |build| + # We need to set build trace and artifacts after saving a build + # (id required), that is why we need `#tap` method instead of passing + # block directly to `Ci::Build#create!`. + + setup_artifacts(build) + setup_build_log(build) + build.save end end - def build_create!(pipeline, opts = {}) - attributes = build_attributes_for(pipeline, opts) + def setup_artifacts(build) + return unless %w[build test].include?(build.stage) - Ci::Build.create!(attributes) do |build| - if opts[:name].start_with?('build') - artifacts_cache_file(artifacts_archive_path) do |file| - build.artifacts_file = file - end + artifacts_cache_file(artifacts_archive_path) do |file| + build.artifacts_file = file + end - artifacts_cache_file(artifacts_metadata_path) do |file| - build.artifacts_metadata = file - end - end + artifacts_cache_file(artifacts_metadata_path) do |file| + build.artifacts_metadata = file + end + end - if %w(running success failed).include?(build.status) - # We need to set build trace after saving a build (id required) - build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") - end + def setup_build_log(build) + if %w(running success failed).include?(build.status) + build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") end end def commit_status_create!(pipeline, opts = {}) - attributes = commit_status_attributes_for(pipeline, opts) + attributes = job_attributes(pipeline, opts) + GenericCommitStatus.create!(attributes) end - def commit_status_attributes_for(pipeline, opts) + def job_attributes(pipeline, opts) { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]), ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline, created_at: Time.now, updated_at: Time.now }.merge(opts) end - def build_attributes_for(pipeline, opts) - commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command') - end - def build_user @project.team.users.sample end @@ -131,8 +146,8 @@ class Gitlab::Seeder::Builds end Gitlab::Seeder.quiet do - Project.all.sample(10).each do |project| - project_builds = Gitlab::Seeder::Builds.new(project) + Project.all.sample(5).each do |project| + project_builds = Gitlab::Seeder::Pipelines.new(project) project_builds.seed! end end diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index d90d7aca4fd..20cd88c8d20 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -67,6 +67,8 @@ use following Markdown code to embed the est coverage report into `README.md`: ![coverage](http://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage) ``` +The latest successful pipeline will be used to read the test coverage value. + [builds]: #builds [jobs]: yaml/README.md#jobs [stages]: yaml/README.md#stages diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb index 3d56ea3e47a..95d925dc7f3 100644 --- a/lib/gitlab/badge/coverage/report.rb +++ b/lib/gitlab/badge/coverage/report.rb @@ -13,8 +13,7 @@ module Gitlab @job = job @pipeline = @project.pipelines - .where(ref: @ref) - .where(sha: @project.commit(@ref).try(:sha)) + .latest_successful_for(@ref) .first end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 44128a43362..a121cb2fc97 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -237,6 +237,56 @@ describe AutocompleteController do end end + context 'authorized projects apply limit' do + before do + authorized_project2 = create(:project) + authorized_project3 = create(:project) + + authorized_project.team << [user, :master] + authorized_project2.team << [user, :master] + authorized_project3.team << [user, :master] + + stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + end + + describe 'GET #projects with project ID' do + before do + get(:projects, project_id: project.id) + end + + let(:body) { JSON.parse(response.body) } + + it do + expect(body).to be_kind_of(Array) + expect(body.size).to eq 3 # Of a total of 4 + end + end + end + + context 'authorized projects with offset' do + before do + authorized_project2 = create(:project) + authorized_project3 = create(:project) + + authorized_project.team << [user, :master] + authorized_project2.team << [user, :master] + authorized_project3.team << [user, :master] + end + + describe 'GET #projects with project ID and offset_id' do + before do + get(:projects, project_id: project.id, offset_id: authorized_project.id) + end + + let(:body) { JSON.parse(response.body) } + + it do + expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there + expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either + end + end + end + context 'authorized projects without admin_issue ability' do before(:each) do authorized_project.team << [user, :guest] diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 8910c50c294..5d777895542 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -572,6 +572,18 @@ describe 'Issue Boards', feature: true, js: true do end end + context 'keyboard shortcuts' do + before do + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + end + + it 'allows user to use keyboard shortcuts' do + find('.boards-list').native.send_keys('i') + expect(page).to have_content('New Issue') + end + end + context 'signed out user' do before do logout diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index af86d3c338a..5972e7f31c2 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -4,12 +4,6 @@ feature 'test coverage badge' do given!(:user) { create(:user) } given!(:project) { create(:project, :private) } - given!(:pipeline) do - create(:ci_pipeline, project: project, - ref: 'master', - sha: project.commit.id) - end - context 'when user has access to view badge' do background do project.team << [user, :developer] @@ -17,8 +11,10 @@ feature 'test coverage badge' do end scenario 'user requests coverage badge image for pipeline' do - create_job(coverage: 100, name: 'test:1') - create_job(coverage: 90, name: 'test:2') + create_pipeline do |pipeline| + create_build(pipeline, coverage: 100, name: 'test:1') + create_build(pipeline, coverage: 90, name: 'test:2') + end show_test_coverage_badge @@ -26,9 +22,11 @@ feature 'test coverage badge' do end scenario 'user requests coverage badge for specific job' do - create_job(coverage: 50, name: 'test:1') - create_job(coverage: 50, name: 'test:2') - create_job(coverage: 85, name: 'coverage') + create_pipeline do |pipeline| + create_build(pipeline, coverage: 50, name: 'test:1') + create_build(pipeline, coverage: 50, name: 'test:2') + create_build(pipeline, coverage: 85, name: 'coverage') + end show_test_coverage_badge(job: 'coverage') @@ -36,7 +34,9 @@ feature 'test coverage badge' do end scenario 'user requests coverage badge for pipeline without coverage' do - create_job(coverage: nil, name: 'test') + create_pipeline do |pipeline| + create_build(pipeline, coverage: nil, name: 'test') + end show_test_coverage_badge @@ -54,10 +54,19 @@ feature 'test coverage badge' do end end - def create_job(coverage:, name:) - create(:ci_build, name: name, - coverage: coverage, - pipeline: pipeline) + def create_pipeline + opts = { project: project, ref: 'master', sha: project.commit.id } + + create(:ci_pipeline, opts).tap do |pipeline| + yield pipeline + pipeline.build_updated + end + end + + def create_build(pipeline, coverage:, name:) + opts = { pipeline: pipeline, coverage: coverage, name: name } + + create(:ci_build, :success, opts) end def show_test_coverage_badge(job: nil) diff --git a/spec/features/projects/branches/delete_spec.rb b/spec/features/projects/branches/delete_spec.rb new file mode 100644 index 00000000000..63878c55421 --- /dev/null +++ b/spec/features/projects/branches/delete_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature 'Delete branch', feature: true, js: true do + include WaitForAjax + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as user + visit namespace_project_branches_path(project.namespace, project) + end + + it 'destroys tooltip' do + first('.remove-row').hover + expect(page).to have_selector('.tooltip') + + first('.remove-row').click + wait_for_ajax + + expect(page).not_to have_selector('.tooltip') + end +end diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 79abba21854..1b14945bf0a 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -20,7 +20,7 @@ describe 'Branches', feature: true do describe 'Find branches' do it 'shows filtered branches', js: true do - visit namespace_project_branches_path(project.namespace, project, project.id) + visit namespace_project_branches_path(project.namespace, project) fill_in 'branch-search', with: 'fix' find('#branch-search').native.send_keys(:enter) diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index 1b4ff6b6f1b..e45e3a36d01 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +include WaitForAjax describe 'Cherry-pick Commits' do let(:project) { create(:project) } @@ -8,12 +9,11 @@ describe 'Cherry-pick Commits' do before do login_as :user project.team << [@user, :master] - visit namespace_project_commits_path(project.namespace, project, project.repository.root_ref, { limit: 5 }) + visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) end context "I cherry-pick a commit" do it do - visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) find("a[href='#modal-cherry-pick-commit']").click expect(page).not_to have_content('v1.0.0') # Only branches, not tags page.within('#modal-cherry-pick-commit') do @@ -26,7 +26,6 @@ describe 'Cherry-pick Commits' do context "I cherry-pick a merge commit" do it do - visit namespace_project_commit_path(project.namespace, project, master_pickable_merge.id) find("a[href='#modal-cherry-pick-commit']").click page.within('#modal-cherry-pick-commit') do uncheck 'create_merge_request' @@ -38,7 +37,6 @@ describe 'Cherry-pick Commits' do context "I cherry-pick a commit that was previously cherry-picked" do it do - visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) find("a[href='#modal-cherry-pick-commit']").click page.within('#modal-cherry-pick-commit') do uncheck 'create_merge_request' @@ -56,7 +54,6 @@ describe 'Cherry-pick Commits' do context "I cherry-pick a commit in a new merge request" do it do - visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) find("a[href='#modal-cherry-pick-commit']").click page.within('#modal-cherry-pick-commit') do click_button 'Cherry-pick' @@ -64,4 +61,28 @@ describe 'Cherry-pick Commits' do expect(page).to have_content('The commit has been successfully cherry-picked. You can now submit a merge request to get this change into the original branch.') end end + + context "I cherry-pick a commit from a different branch", js: true do + it do + find('.commit-action-buttons a.dropdown-toggle').click + find(:css, "a[href='#modal-cherry-pick-commit']").click + + page.within('#modal-cherry-pick-commit') do + click_button 'master' + end + + wait_for_ajax + + page.within('#modal-cherry-pick-commit .dropdown-menu .dropdown-content') do + click_link 'feature' + end + + page.within('#modal-cherry-pick-commit') do + uncheck 'create_merge_request' + click_button 'Cherry-pick' + end + + expect(page).to have_content('The commit has been successfully cherry-picked.') + end + end end diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index 4f3304f7b6d..fdce4e714ff 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -51,6 +51,28 @@ describe MoveToProjectFinder do expect(subject.execute(project).to_a).to eq([other_reporter_project]) end + + it 'returns a page of projects ordered by id in descending order' do + stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + + reporter_project.team << [user, :reporter] + developer_project.team << [user, :developer] + master_project.team << [user, :master] + + expect(subject.execute(project).to_a).to eq([master_project, developer_project]) + end + + it 'returns projects after the given offset id' do + stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + + reporter_project.team << [user, :reporter] + developer_project.team << [user, :developer] + master_project.team << [user, :master] + + expect(subject.execute(project, search: nil, offset_id: master_project.id).to_a).to eq([developer_project, reporter_project]) + expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project]) + expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty + end end context 'search' do diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb new file mode 100644 index 00000000000..2dd2eab0524 --- /dev/null +++ b/spec/helpers/issuables_helper_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe IssuablesHelper do + let(:label) { build_stubbed(:label) } + let(:label2) { build_stubbed(:label) } + + context 'label tooltip' do + it 'returns label text' do + expect(issuable_labels_tooltip([label])).to eq(label.title) + end + + it 'returns label text' do + expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more") + end + end +end diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml new file mode 100644 index 00000000000..397bdc85c67 --- /dev/null +++ b/spec/javascripts/fixtures/issue_sidebar_label.html.haml @@ -0,0 +1,16 @@ +.block.labels + .sidebar-collapsed-icon.js-sidebar-labels-tooltip + .title.hide-collapsed + %a.edit-link.pull-right{ href: "#" } + Edit + .selectbox.hide-collapsed{ style: "display: none;" } + .dropdown + %button.dropdown-menu-toggle.js-label-select.js-multiselect{ type: "button", data: { ability_name: "issue", field_name: "issue[label_names][]", issue_update: "/root/test/issues/2.json", labels: "/root/test/labels.json", project_id: "12", show_any: "true", show_no: "true", toggle: "dropdown" } } + %span.dropdown-toggle-text + Label + %i.fa.fa-chevron-down + .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable + .dropdown-page-one + .dropdown-content + .dropdown-loading + %i.fa.fa-spinner.fa-spin diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6 new file mode 100644 index 00000000000..840c7b6d015 --- /dev/null +++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6 @@ -0,0 +1,89 @@ +//= require lib/utils/type_utility +//= require jquery +//= require bootstrap +//= require gl_dropdown +//= require select2 +//= require jquery.nicescroll +//= require api +//= require create_label +//= require issuable_context +//= require users_select +//= require labels_select + +(() => { + let saveLabelCount = 0; + describe('Issue dropdown sidebar', () => { + fixture.preload('issue_sidebar_label.html'); + + beforeEach(() => { + fixture.load('issue_sidebar_label.html'); + new IssuableContext('{"id":1,"name":"Administrator","username":"root"}'); + new LabelsSelect(); + + spyOn(jQuery, 'ajax').and.callFake((req) => { + const d = $.Deferred(); + let LABELS_DATA = [] + + if (req.url === '/root/test/labels.json') { + for (let i = 0; i < 10; i++) { + LABELS_DATA.push({id: i, title: `test ${i}`, color: '#5CB85C'}); + } + } else if (req.url === '/root/test/issues/2.json') { + let tmp = [] + for (let i = 0; i < saveLabelCount; i++) { + tmp.push({id: i, title: `test ${i}`, color: '#5CB85C'}); + } + LABELS_DATA = {labels: tmp}; + } + + d.resolve(LABELS_DATA); + return d.promise(); + }); + }); + + it('changes collapsed tooltip when changing labels when less than 5', (done) => { + saveLabelCount = 5; + $('.edit-link').get(0).click(); + + setTimeout(() => { + expect($('.dropdown-content a').length).toBe(10); + + $('.dropdow-content a').each((i, $link) => { + if (i < 5) { + $link.get(0).click(); + } + }); + + $('.edit-link').get(0).click(); + + setTimeout(() => { + expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe('test 0, test 1, test 2, test 3, test 4'); + done(); + }, 0); + }, 0); + }); + + it('changes collapsed tooltip when changing labels when more than 5', (done) => { + saveLabelCount = 6; + $('.edit-link').get(0).click(); + + setTimeout(() => { + expect($('.dropdown-content a').length).toBe(10); + + $('.dropdow-content a').each((i, $link) => { + if (i < 5) { + $link.get(0).click(); + } + }); + + $('.edit-link').get(0).click(); + + setTimeout(() => { + expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe('test 0, test 1, test 2, test 3, test 4, and 1 more'); + done(); + }, 0); + }, 0); + }); + }); +})(); + diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb index 1ff49602486..ab0cce6e091 100644 --- a/spec/lib/gitlab/badge/coverage/report_spec.rb +++ b/spec/lib/gitlab/badge/coverage/report_spec.rb @@ -44,45 +44,49 @@ describe Gitlab::Badge::Coverage::Report do end end - context 'pipeline exists' do - let!(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.id, - ref: 'master') - end + context 'when latest successful pipeline exists' do + before do + create_pipeline do |pipeline| + create(:ci_build, :success, pipeline: pipeline, name: 'first', coverage: 40) + create(:ci_build, :success, pipeline: pipeline, coverage: 60) + end - context 'builds exist' do - before do - create(:ci_build, name: 'first', pipeline: pipeline, coverage: 40) - create(:ci_build, pipeline: pipeline, coverage: 60) + create_pipeline do |pipeline| + create(:ci_build, :failed, pipeline: pipeline, coverage: 10) end + end - context 'particular job specified' do - let(:job_name) { 'first' } + context 'when particular job specified' do + let(:job_name) { 'first' } - it 'returns coverage for the particular job' do - expect(badge.status).to eq 40 - end + it 'returns coverage for the particular job' do + expect(badge.status).to eq 40 end + end - context 'particular job not specified' do - let(:job_name) { '' } + context 'when particular job not specified' do + let(:job_name) { '' } + + it 'returns arithemetic mean for the pipeline' do + expect(badge.status).to eq 50 + end + end + end - it 'returns arithemetic mean for the pipeline' do - expect(badge.status).to eq 50 - end + context 'when only failed pipeline exists' do + before do + create_pipeline do |pipeline| + create(:ci_build, :failed, pipeline: pipeline, coverage: 10) end end - context 'builds do not exist' do - it_behaves_like 'unknown coverage report' + it_behaves_like 'unknown coverage report' - context 'particular job specified' do - let(:job_name) { 'nonexistent' } + context 'particular job specified' do + let(:job_name) { 'nonexistent' } - it 'retruns nil' do - expect(badge.status).to be_nil - end + it 'retruns nil' do + expect(badge.status).to be_nil end end end @@ -90,4 +94,13 @@ describe Gitlab::Badge::Coverage::Report do context 'pipeline does not exist' do it_behaves_like 'unknown coverage report' end + + def create_pipeline + opts = { project: project, sha: project.commit.id, ref: 'master' } + + create(:ci_pipeline, opts).tap do |pipeline| + yield pipeline + pipeline.build_updated + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f7dbfd712cc..1fea50ad42c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -719,6 +719,14 @@ describe Repository, models: true do expect(merge_commit).to be_present expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present end + + it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do + merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) + merge_commit_id = repository.merge(user, merge_request, commit_options) + repository.commit(merge_commit_id) + + expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) + end end describe '#revert' do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 18da3b1b453..f81a58899fd 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1113,6 +1113,46 @@ describe NotificationService, services: true do end end + describe 'GroupMember' do + describe '#decline_group_invite' do + let(:creator) { create(:user) } + let(:group) { create(:group) } + let(:member) { create(:user) } + + before(:each) do + group.add_owner(creator) + group.add_developer(member, creator) + end + + it do + group_member = group.members.first + + expect do + notification.decline_group_invite(group_member) + end.to change { ActionMailer::Base.deliveries.size }.by(1) + end + end + end + + describe 'ProjectMember' do + describe '#decline_group_invite' do + let(:project) { create(:project) } + let(:member) { create(:user) } + + before(:each) do + project.team << [member, :developer, project.owner] + end + + it do + project_member = project.members.first + + expect do + notification.decline_project_invite(project_member) + end.to change { ActionMailer::Base.deliveries.size }.by(1) + end + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/vendor/assets/javascripts/Chart.js b/vendor/assets/javascripts/Chart.js index c264262ba73..c264262ba73 100755..100644 --- a/vendor/assets/javascripts/Chart.js +++ b/vendor/assets/javascripts/Chart.js diff --git a/vendor/assets/javascripts/autosize.js b/vendor/assets/javascripts/autosize.js index cfa49e72c50..cfa49e72c50 100755..100644 --- a/vendor/assets/javascripts/autosize.js +++ b/vendor/assets/javascripts/autosize.js diff --git a/vendor/assets/javascripts/jquery.scrollTo.js b/vendor/assets/javascripts/jquery.scrollTo.js index 7ba17766b70..7ba17766b70 100755..100644 --- a/vendor/assets/javascripts/jquery.scrollTo.js +++ b/vendor/assets/javascripts/jquery.scrollTo.js |