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 ...
This commit is contained in:
commit
a49151f0d1
11
CHANGELOG
11
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]; });
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -63,9 +63,10 @@
|
|||
&.image_file {
|
||||
background: #eee;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
padding: 100px;
|
||||
max-width: 50%;
|
||||
padding: 20px;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.modal-body {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
|
||||
.form-actions {
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
font-weight: normal;
|
||||
background-color: #eee;
|
||||
color: #78a;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
min-width: 175px;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
.select2-results .select2-result-label,
|
||||
.select2-more-results {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,15 @@
|
|||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin: 0 2px 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
|
|
|
@ -34,11 +34,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wiki {
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -374,3 +374,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.merge-request-details {
|
||||
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
After Width: | Height: | Size: 227 B |
|
@ -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.' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
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
|
||||
rescue
|
||||
[]
|
||||
end
|
||||
|
||||
def create_pipelines_for(project, ref)
|
||||
commits = project.repository.commits(ref, limit: 5)
|
||||
create_pipelines(project, ref, commits)
|
||||
end
|
||||
|
||||
def create_pipelines(project, ref, commits)
|
||||
commits.map do |commit|
|
||||
project.pipelines.create(sha: commit.id, ref: ref)
|
||||
def create_master_pipelines
|
||||
@project.repository.commits('master', limit: 4).map do |commit|
|
||||
create_pipeline!(@project, 'master', commit)
|
||||
end
|
||||
rescue
|
||||
[]
|
||||
end
|
||||
|
||||
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_pipeline!(project, ref, commit)
|
||||
project.pipelines.create(sha: commit.id, ref: ref)
|
||||
end
|
||||
|
||||
def build_create!(pipeline, opts = {})
|
||||
attributes = build_attributes_for(pipeline, opts)
|
||||
attributes = job_attributes(pipeline, opts)
|
||||
.merge(commands: '$ build command')
|
||||
|
||||
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
|
||||
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!`.
|
||||
|
||||
artifacts_cache_file(artifacts_metadata_path) do |file|
|
||||
build.artifacts_metadata = file
|
||||
end
|
||||
end
|
||||
setup_artifacts(build)
|
||||
setup_build_log(build)
|
||||
build.save
|
||||
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_artifacts(build)
|
||||
return unless %w[build test].include?(build.stage)
|
||||
|
||||
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
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -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 'builds exist' do
|
||||
before do
|
||||
create(:ci_build, name: 'first', pipeline: pipeline, coverage: 40)
|
||||
create(:ci_build, pipeline: pipeline, coverage: 60)
|
||||
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 'particular job specified' do
|
||||
let(:job_name) { 'first' }
|
||||
|
||||
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) { '' }
|
||||
|
||||
it 'returns arithemetic mean for the pipeline' do
|
||||
expect(badge.status).to eq 50
|
||||
end
|
||||
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'
|
||||
context 'when particular job specified' do
|
||||
let(:job_name) { 'first' }
|
||||
|
||||
context 'particular job specified' do
|
||||
let(:job_name) { 'nonexistent' }
|
||||
it 'returns coverage for the particular job' do
|
||||
expect(badge.status).to eq 40
|
||||
end
|
||||
end
|
||||
|
||||
it 'retruns nil' do
|
||||
expect(badge.status).to be_nil
|
||||
end
|
||||
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
|
||||
|
||||
context 'when only failed pipeline exists' do
|
||||
before do
|
||||
create_pipeline do |pipeline|
|
||||
create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'unknown coverage report'
|
||||
|
||||
context 'particular job specified' do
|
||||
let(:job_name) { 'nonexistent' }
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue