Commit cd0e25e3 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'ce/master' into rc/ce-to-ee-friday

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 1d510fd8 82f6c0f5
...@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0' ...@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.2' gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
......
...@@ -352,7 +352,7 @@ GEM ...@@ -352,7 +352,7 @@ GEM
temple (~> 0.7.6) temple (~> 0.7.6)
thor thor
tilt tilt
hashie (3.5.1) hashie (3.5.5)
health_check (2.2.1) health_check (2.2.1)
rails (>= 4.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
...@@ -465,7 +465,7 @@ GEM ...@@ -465,7 +465,7 @@ GEM
octokit (4.6.2) octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4) oj (2.17.4)
omniauth (1.3.2) omniauth (1.4.2)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1) omniauth-auth0 (1.4.1)
...@@ -951,7 +951,7 @@ DEPENDENCIES ...@@ -951,7 +951,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.6.2) octokit (~> 4.6.2)
oj (~> 2.17.4) oj (~> 2.17.4)
omniauth (~> 1.3.2) omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0) omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
......
...@@ -7,9 +7,7 @@ ...@@ -7,9 +7,7 @@
/* global Aside */ /* global Aside */
window.$ = window.jQuery = require('jquery'); window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/draggable'); require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable'); require('jquery-ui/ui/sortable');
require('jquery-ujs'); require('jquery-ujs');
require('vendor/jquery.endless-scroll'); require('vendor/jquery.endless-scroll');
...@@ -107,6 +105,7 @@ require('./droplab/droplab_filter'); ...@@ -107,6 +105,7 @@ require('./droplab/droplab_filter');
require('./abuse_reports'); require('./abuse_reports');
require('./activities'); require('./activities');
require('./admin'); require('./admin');
require('./ajax_loading_spinner');
require('./api'); require('./api');
require('./aside'); require('./aside');
require('./autosave'); require('./autosave');
......
...@@ -123,14 +123,18 @@ class List { ...@@ -123,14 +123,18 @@ class List {
if (listFrom) { if (listFrom) {
this.issuesSize += 1; this.issuesSize += 1;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id) this.updateIssueLabel(issue, listFrom);
.then(() => {
listFrom.getIssues(false);
});
} }
} }
} }
updateIssueLabel(issue, listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
findIssue (id) { findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0]; return this.issues.filter(issue => issue.id === id)[0];
} }
......
...@@ -97,9 +97,12 @@ ...@@ -97,9 +97,12 @@
const issueLists = issue.getLists(); const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label); const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
if (!issueTo) { if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex); listTo.addIssue(issue, listFrom, newIndex);
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
} }
if (listTo.type === 'done') { if (listTo.type === 'done') {
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
/* global AdminEmailSelect */ /* global AdminEmailSelect */
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -287,6 +288,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -287,6 +288,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show': case 'ci:lints:show':
new gl.CILintEditor(); new gl.CILintEditor();
break; break;
case 'users:show':
new UserCallout();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
...@@ -326,6 +330,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -326,6 +330,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation(); shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break; break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
......
...@@ -78,7 +78,6 @@ ...@@ -78,7 +78,6 @@
} else { } else {
$(element).find('.assignee-icon').empty(); $(element).find('.assignee-icon').empty();
} }
return $(element).effect('highlight');
}; };
function Milestone() { function Milestone() {
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
...@@ -20,15 +20,35 @@ ...@@ -20,15 +20,35 @@
}; };
NewBranchForm.prototype.init = function() { NewBranchForm.prototype.init = function() {
if (this.name.val().length > 0) { if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur'); return this.name.trigger('blur');
} }
}; };
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) { NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
return this.ref.autocomplete({ var $branchSelect = $('.js-branch-select');
source: availableRefs,
minLength: 1 $branchSelect.glDropdown({
data: availableRefs,
filterable: true,
filterByText: true,
remote: false,
fieldName: $branchSelect.data('field-name'),
selectable: true,
isSelectable: function(branch, $el) {
return !$el.hasClass('is-active');
},
text: function(branch) {
return branch;
},
id: function(branch) {
return branch;
},
toggleLabel: function(branch) {
if (branch) {
return branch;
}
}
}); });
}; };
......
// require everything else in this directory require('./gl_crop');
function requireAll(context) { return context.keys().map(context); } require('./profile');
requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
...@@ -93,6 +93,9 @@ ...@@ -93,6 +93,9 @@
$.scrollTo(0); $.scrollTo(0);
new Flash('Failed to update branch!'); new Flash('Failed to update branch!');
} }
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
}); });
} }
......
// require everything else in this directory require('./protected_branch_access_dropdown');
function requireAll(context) { return context.keys().map(context); } require('./protected_branch_create');
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/)); require('./protected_branch_dropdown');
require('./protected_branch_edit');
require('./protected_branch_edit_list');
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */ /* global ace */
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
(function() { (function() {
$(function() { $(function() {
var editor = ace.edit("editor"); var editor = ace.edit("editor");
......
/* global Cookies */
const userCalloutElementName = '.user-callout';
const closeButton = '.close-user-callout';
const userCalloutBtn = '.user-callout-btn';
const userCalloutSvgAttrName = 'callout-svg';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const USER_CALLOUT_TEMPLATE = `
<div class="bordered-box landing content-block">
<button class="btn btn-default close close-user-callout" type="button">
<i class="fa fa-times dismiss-icon"></i>
</button>
<div class="row">
<div class="col-sm-3 col-xs-12 svg-container">
</div>
<div class="col-sm-8 col-xs-12 inner-content">
<h4>
Customize your experience
</h4>
<p>
Change syntax themes, default project pages, and more in preferences.
</p>
<a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
</div>
</div>
</div>`;
class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $(userCalloutElementName);
this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
$(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
this.init();
}
init() {
const $template = $(USER_CALLOUT_TEMPLATE);
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$template.find('.svg-container').append(this.userCalloutSvg);
this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
}
}
dismissCallout(e) {
Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty();
}
}
}
module.exports = UserCallout;
// require everything else in this directory require('./calendar');
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
/* global Vue, Flash, gl */ /* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign, no-alert */
((gl) => { ((gl) => {
gl.VuePipelineActions = Vue.extend({ gl.VuePipelineActions = Vue.extend({
...@@ -16,6 +16,20 @@ ...@@ -16,6 +16,20 @@
download(name) { download(name) {
return `Download ${name} artifacts`; return `Download ${name} artifacts`;
}, },
/**
* Shows a dialog when the user clicks in the cancel button.
* We need to prevent the default behavior and stop propagation because the
* link relies on UJS.
*
* @param {Event} event
*/
confirmAction(event) {
if (!confirm('Are you sure you want to cancel this pipeline?')) {
event.preventDefault();
event.stopPropagation();
}
},
}, },
template: ` template: `
<td class="pipeline-actions hidden-xs"> <td class="pipeline-actions hidden-xs">
...@@ -87,6 +101,7 @@ ...@@ -87,6 +101,7 @@
</a> </a>
<a <a
v-if='pipeline.flags.cancelable' v-if='pipeline.flags.cancelable'
@click="confirmAction"
class="btn btn-remove has-tooltip" class="btn btn-remove has-tooltip"
title="Cancel" title="Cancel"
rel="nofollow" rel="nofollow"
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory * This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope. * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery-ui/autocomplete
*= require jquery.atwho *= require jquery.atwho
*= require select2 *= require select2
*= require_self *= require_self
......
...@@ -2,17 +2,6 @@ ...@@ -2,17 +2,6 @@
font-family: $regular_font; font-family: $regular_font;
font-size: $font-size-base; font-size: $font-size-base;
&.ui-autocomplete {
border-color: $jq-ui-border;
padding: 0;
margin-top: 2px;
z-index: 1001;
.ui-menu-item a {
padding: 4px 10px;
}
}
.ui-state-default { .ui-state-default {
border: 1px solid $white-light; border: 1px solid $white-light;
background: $white-light; background: $white-light;
......
...@@ -277,3 +277,41 @@ table.u2f-registrations { ...@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px; padding-left: 18px;
} }
} }
.user-callout {
margin: 24px auto 0;
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.landing {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
}
}
}
...@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base ...@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do scope :in_projects, ->(projects) do
where(project_id: projects).recent where(project_id: projects.pluck(:id)).recent
end end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) } scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
......
...@@ -15,10 +15,10 @@ class MattermostService < ChatNotificationService ...@@ -15,10 +15,10 @@ class MattermostService < ChatNotificationService
'This service sends notifications about projects events to Mattermost channels.<br /> 'This service sends notifications about projects events to Mattermost channels.<br />
To set up this service: To set up this service:
<ol> <ol>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li> <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation.</li>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li> <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event.</li>
<li>Paste the webhook <strong>URL</strong> into the field bellow. </li> <li>Paste the webhook <strong>URL</strong> into the field below.</li>
<li>Select events below to enable notifications. The channel and username are optional. </li> <li>Select events below to enable notifications. The <strong>Channel handle</strong> and <strong>Username</strong> fields are optional.</li>
</ol>' </ol>'
end end
...@@ -28,14 +28,14 @@ class MattermostService < ChatNotificationService ...@@ -28,14 +28,14 @@ class MattermostService < ChatNotificationService
def default_fields def default_fields
[ [
{ type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' }, { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' }, { type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' }, { type: 'checkbox', name: 'notify_only_broken_pipelines' },
] ]
end end
def default_channel_placeholder def default_channel_placeholder
"town-square" "Channel handle (e.g. town-square)"
end end
end end
...@@ -13,11 +13,11 @@ class SlackService < ChatNotificationService ...@@ -13,11 +13,11 @@ class SlackService < ChatNotificationService
def help def help
'This service sends notifications about projects events to Slack channels.<br /> 'This service sends notifications about projects events to Slack channels.<br />
To setup this service: To set up this service:
<ol> <ol>
<li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li> <li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below. </li> <li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications. The channel and username are optional. </li> <li>Select events below to enable notifications. The <strong>Channel name</strong> and <strong>Username</strong> fields are optional.</li>
</ol>' </ol>'
end end
...@@ -27,14 +27,14 @@ class SlackService < ChatNotificationService ...@@ -27,14 +27,14 @@ class SlackService < ChatNotificationService
def default_fields def default_fields
[ [
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }, { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' }, { type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' }, { type: 'checkbox', name: 'notify_only_broken_pipelines' },
] ]
end end
def default_channel_placeholder def default_channel_placeholder
"#general" "Channel name (e.g. general)"
end end
end end
...@@ -116,9 +116,7 @@ class Repository ...@@ -116,9 +116,7 @@ class Repository
offset: offset, offset: offset,
after: after, after: after,
before: before, before: before,
# --follow doesn't play well with --skip. See: follow: path.present?,
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false,
skip_merges: skip_merges skip_merges: skip_merges
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
- page_title "Projects" - page_title "Projects"
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
- if @projects.any? || params[:filter_projects] - if @projects.any? || params[:filter_projects]
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
......
...@@ -101,5 +101,3 @@ ...@@ -101,5 +101,3 @@
$("#created-personal-access-token").click(function() { $("#created-personal-access-token").click(function() {
this.select(); this.select();
}); });
$("#created-personal-access-token").effect('highlight', { color: '#ffff99' }, 2000);
...@@ -12,12 +12,16 @@ ...@@ -12,12 +12,16 @@
.form-group .form-group
= label_tag :branch_name, nil, class: 'control-label' = label_tag :branch_name, nil, class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name' = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name'
.help-block.text-danger.js-branch-name-error .help-block.text-danger.js-branch-name-error
.form-group .form-group
= label_tag :ref, 'Create from', class: 'control-label' = label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' = hidden_field_tag :ref, params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
data: { selected: params[:ref] || @project.default_branch, field_name: 'ref' } })
.help-block Existing branch name, tag, or commit SHA .help-block Existing branch name, tag, or commit SHA
.form-actions .form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
......
...@@ -9,7 +9,11 @@ ...@@ -9,7 +9,11 @@
.form-group .form-group
= f.label :ref, 'Create for', class: 'control-label' = f.label :ref, 'Create for', class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :ref, required: true, tabindex: 2, class: 'form-control js-branch-name ui-autocomplete-input', autocomplete: :false, id: :ref = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block Existing branch name, tag .help-block Existing branch name, tag
.form-actions .form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112 90" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><rect width="112" height="90" fill="#fff" rx="6"/><path fill="#eee" fill-rule="nonzero" d="m4 6.01v77.98c0 1.11.899 2.01 2 2.01h100c1.105 0 2-.898 2-2.01v-77.98c0-1.11-.899-2.01-2-2.01h-100c-1.105 0-2 .898-2 2.01m-4 0c0-3.319 2.686-6.01 6-6.01h100c3.315 0 6 2.694 6 6.01v77.98c0 3.319-2.686 6.01-6 6.01h-100c-3.315 0-6-2.694-6-6.01v-77.98"/><g transform="translate(26 35)"><rect width="4" height="39" x="5" fill="#eee" rx="2" id="0"/><rect width="4" height="21" x="5" y="18" fill="#fef0ea" rx="2"/><circle cx="7" cy="13" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 20c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(49 35)"><use xlink:href="#0"/><rect width="4" height="21" x="5" y="18" fill="#b5a7dd" rx="2"/><circle cx="7" cy="25" r="5" fill="#fff"/><path fill="#6b4fbb" fill-rule="nonzero" d="m7 32c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(72 33)"><rect width="4" height="39" x="5" y="2" fill="#eee" rx="2"/><rect width="4" height="34" x="5" y="7" fill="#fef0ea" rx="2"/><circle cx="7" cy="7" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 14c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g fill="#6b4fbb"><circle cx="13.5" cy="11.5" r="2.5"/><circle cx="23.5" cy="11.5" r="2.5" opacity=".5"/><circle cx="33.5" cy="11.5" r="2.5" opacity=".5"/></g><path fill="#eee" d="m0 19h111v4h-111z"/></g></svg>
...@@ -98,6 +98,7 @@ ...@@ -98,6 +98,7 @@
Snippets Snippets
%div{ class: container_class } %div{ class: container_class }
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
......
---
title: Make Git history follow renames again by performing the --skip in Ruby
merge_request:
author:
---
title: 'Add performance query regression fix for !9088 affecting #27267'
merge_request:
author:
---
title: Add Runner's registration/deletion v4 API
merge_request: 9246
author:
---
title: Removes label when moving issue to another list that it is currently in
merge_request:
author:
---
title: Removed jQuery UI highlight & autocomplete
merge_request:
author:
---
title: Bump Hashie to 3.5.5 and omniauth to 1.4.2 to eliminate warning noise
merge_request:
author:
---
title: 'API: Return 400 for all validation erros in the mebers API'
merge_request: 9523
author: Robert Schilling
---
title: update Vue to v2.1.10
merge_request: 9386
author:
---
title: Added user callouts to the projects dashboard and user profile
merge_request:
author:
...@@ -88,7 +88,7 @@ var config = { ...@@ -88,7 +88,7 @@ var config = {
'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap', 'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap',
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'), 'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'), 'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
'vue$': IS_PRODUCTION ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js', 'vue$': 'vue/dist/vue.common.js',
} }
} }
} }
......
...@@ -42,5 +42,6 @@ changes are in V4: ...@@ -42,5 +42,6 @@ changes are in V4:
- Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736) - Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736)
- Remove the ProjectGitHook API. Use the ProjectPushRule API instead [!1301](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1301) - Remove the ProjectGitHook API. Use the ProjectPushRule API instead [!1301](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1301)
- Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384) - Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384)
- Return HTTP status code `400` for all validation errors when creating or updating a member instead of sometimes `422` error. [!9523](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9523)
- Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505) - Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505)
- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) - Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449)
\ No newline at end of file
...@@ -1018,7 +1018,7 @@ A simple example: ...@@ -1018,7 +1018,7 @@ A simple example:
```yaml ```yaml
job1: job1:
coverage: /Code coverage: \d+\.\d+/ coverage: '/Code coverage: \d+\.\d+/'
``` ```
## Git Strategy ## Git Strategy
......
This diff is collapsed.
...@@ -24,23 +24,24 @@ There, you will see a checkbox with the following events that can be triggered: ...@@ -24,23 +24,24 @@ There, you will see a checkbox with the following events that can be triggered:
- Push - Push
- Issue - Issue
- Confidential issue
- Merge request - Merge request
- Note - Note
- Tag push - Tag push
- Build - Build
- Pipeline
- Wiki page - Wiki page
Bellow each of these event checkboxes, you will have an input field to insert Below each of these event checkboxes, you have an input field to enter
which Mattermost channel you want to send that event message, with `#town-square` which Mattermost channel you want to send that event message. Enter your preferred channel handle (the hash sign `#` is optional).
being the default. The hash sign is optional.
At the end, fill in your Mattermost details: At the end, fill in your Mattermost details:
| Field | Description | | Field | Description |
| ----- | ----------- | | ----- | ----------- |
| **Webhook** | The incoming webhooks which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo... | | **Webhook** | The incoming webhook URL which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo… |
| **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. | | **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. |
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. | | **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. |
![Mattermost configuration](img/mattermost_configuration.png) ![Mattermost configuration](img/mattermost_configuration.png)
...@@ -21,23 +21,25 @@ There, you will see a checkbox with the following events that can be triggered: ...@@ -21,23 +21,25 @@ There, you will see a checkbox with the following events that can be triggered:
- Push - Push
- Issue - Issue
- Confidential issue
- Merge request - Merge request
- Note - Note
- Tag push - Tag push
- Build - Build
- Pipeline
- Wiki page - Wiki page
Bellow each of these event checkboxes, you will have an input field to insert Below each of these event checkboxes, you have an input field to enter
which Slack channel you want to send that event message, with `#general` which Slack channel you want to send that event message. Enter your preferred channel name **without** the hash sign (`#`).
being the default. Enter your preferred channel **without** the hash sign (`#`).
At the end, fill in your Slack details: At the end, fill in your Slack details:
| Field | Description | | Field | Description |
| ----- | ----------- | | ----- | ----------- |
| **Webhook** | The [incoming webhook URL][slackhook] which you have to setup on Slack. | | **Webhook** | The [incoming webhook URL][slackhook] which you have to setup on Slack. |
| **Username** | Optional username which can be on messages sent to slack. Fill this in if you want to change the username of the bot. | | **Username** | Optional username which can be on messages sent to Slack. Fill this in if you want to change the username of the bot. |
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. | | **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. |
After you are all done, click **Save changes** for the changes to take effect. After you are all done, click **Save changes** for the changes to take effect.
......
...@@ -63,6 +63,12 @@ git commit -am "Added Debian iso" # commit the file meta data ...@@ -63,6 +63,12 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server git push origin master # sync the git repo and large file to the GitLab server
``` ```
>**Note**: Make sure that `.gitattributes` is tracked by git. Otherwise Git
LFS will not be working properly for people cloning the project.
```bash
git add .gitattributes
```
Cloning the repository works the same as before. Git automatically detects the Cloning the repository works the same as before. Git automatically detects the
LFS-tracked files and clones them via HTTP. If you performed the git clone LFS-tracked files and clones them via HTTP. If you performed the git clone
command with a SSH URL, you have to enter your GitLab credentials for HTTP command with a SSH URL, you have to enter your GitLab credentials for HTTP
......
...@@ -13,6 +13,7 @@ Feature: Project Commits Branches ...@@ -13,6 +13,7 @@ Feature: Project Commits Branches
Given I visit project protected branches page Given I visit project protected branches page
Then I should see "Shop" protected branches list Then I should see "Shop" protected branches list
@javascript
Scenario: I create a branch Scenario: I create a branch
Given I visit project branches page Given I visit project branches page
And I click new branch link And I click new branch link
...@@ -33,12 +34,7 @@ Feature: Project Commits Branches ...@@ -33,12 +34,7 @@ Feature: Project Commits Branches
And I submit new branch form with invalid name And I submit new branch form with invalid name
Then I should see new an error that branch is invalid Then I should see new an error that branch is invalid
Scenario: I create a branch with invalid reference @javascript
Given I visit project branches page
And I click new branch link
And I submit new branch form with invalid reference
Then I should see new an error that ref is invalid
Scenario: I create a branch that already exists Scenario: I create a branch that already exists
Given I visit project branches page Given I visit project branches page
And I click new branch link And I click new branch link
......
...@@ -34,25 +34,19 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps ...@@ -34,25 +34,19 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step 'I submit new branch form' do step 'I submit new branch form' do
fill_in 'branch_name', with: 'deploy_keys' fill_in 'branch_name', with: 'deploy_keys'
fill_in 'ref', with: 'master' select_branch('master')
click_button 'Create branch' click_button 'Create branch'
end end
step 'I submit new branch form with invalid name' do step 'I submit new branch form with invalid name' do
fill_in 'branch_name', with: '1.0 stable' fill_in 'branch_name', with: '1.0 stable'
fill_in 'ref', with: 'master' select_branch('master')
click_button 'Create branch'
end
step 'I submit new branch form with invalid reference' do
fill_in 'branch_name', with: 'foo'
fill_in 'ref', with: 'foo'
click_button 'Create branch' click_button 'Create branch'
end end
step 'I submit new branch form with branch that already exists' do step 'I submit new branch form with branch that already exists' do
fill_in 'branch_name', with: 'master' fill_in 'branch_name', with: 'master'
fill_in 'ref', with: 'master' select_branch('master')
click_button 'Create branch' click_button 'Create branch'
end end
...@@ -65,10 +59,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps ...@@ -65,10 +59,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
expect(page).to have_content "can't contain spaces" expect(page).to have_content "can't contain spaces"
end end
step 'I should see new an error that ref is invalid' do
expect(page).to have_content 'Invalid reference name'
end
step 'I should see new an error that branch already exists' do step 'I should see new an error that branch already exists' do
expect(page).to have_content 'Branch already exists' expect(page).to have_content 'Branch already exists'
end end
...@@ -88,4 +78,12 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps ...@@ -88,4 +78,12 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step "I should not see branch 'improve/awesome'" do step "I should not see branch 'improve/awesome'" do
expect(page.all(visible: true)).not_to have_content 'improve/awesome' expect(page.all(visible: true)).not_to have_content 'improve/awesome'
end end
def select_branch(branch_name)
click_button 'master'
page.within '#new-branch-form .dropdown-menu' do
click_link branch_name
end
end
end end
...@@ -97,6 +97,7 @@ module API ...@@ -97,6 +97,7 @@ module API
mount ::API::Projects mount ::API::Projects
mount ::API::ProjectSnippets mount ::API::ProjectSnippets
mount ::API::Repositories mount ::API::Repositories
mount ::API::Runner
mount ::API::Runners mount ::API::Runners
mount ::API::Services mount ::API::Services
mount ::API::Session mount ::API::Session
......
...@@ -683,6 +683,10 @@ module API ...@@ -683,6 +683,10 @@ module API
end end
end end
class RunnerRegistrationDetails < Grape::Entity
expose :id, :token
end
class BuildArtifactFile < Grape::Entity class BuildArtifactFile < Grape::Entity
expose :filename, :size expose :filename, :size
end end
......
module API
module Helpers
module Runner
def runner_registration_token_valid?
ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
current_application_settings.runners_registration_token)
end
def get_runner_version_from_params
return unless params['info'].present?
attributes_for_keys(%w(name version revision platform architecture), params['info'])
end
def authenticate_runner!
forbidden! unless current_runner
end
def current_runner
@runner ||= ::Ci::Runner.find_by_token(params[:token].to_s)
end
end
end
end
...@@ -61,7 +61,6 @@ module API ...@@ -61,7 +61,6 @@ module API
## EE specific ## EE specific
member = source.members.find_by(user_id: params[:user_id]) member = source.members.find_by(user_id: params[:user_id])
conflict!('Member already exists') if member conflict!('Member already exists') if member
member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
...@@ -69,9 +68,6 @@ module API ...@@ -69,9 +68,6 @@ module API
if member.persisted? && member.valid? if member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member present member.user, with: Entities::Member, member: member
else else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member) render_validation_error!(member)
end end
end end
...@@ -93,9 +89,6 @@ module API ...@@ -93,9 +89,6 @@ module API
if member.update_attributes(declared_params(include_missing: false)) if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member present member.user, with: Entities::Member, member: member
else else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member) render_validation_error!(member)
end end
end end
......
module API
class Runner < Grape::API
helpers ::API::Helpers::Runner
resource :runners do
desc 'Registers a new Runner' do
success Entities::RunnerRegistrationDetails
http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: 'Registration token'
optional :description, type: String, desc: %q(Runner's description)
optional :info, type: Hash, desc: %q(Runner's metadata)
optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
end
post '/' do
attributes = attributes_for_keys [:description, :locked, :run_untagged, :tag_list]
runner =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
Ci::Runner.create(attributes.merge(is_shared: true))
elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for project.
project.runners.create(attributes)
end
return forbidden! unless runner
if runner.id
runner.update(get_runner_version_from_params)
present runner, with: Entities::RunnerRegistrationDetails
else
not_found!
end
end
desc 'Deletes a registered Runner' do
http_codes [[200, 'Runner was deleted'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
delete '/' do
authenticate_runner!
Ci::Runner.find_by_token(params[:token]).destroy
end
end
end
end
...@@ -324,24 +324,30 @@ module Gitlab ...@@ -324,24 +324,30 @@ module Gitlab
end end
def log_by_shell(sha, options) def log_by_shell(sha, options)
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} log) limit = options[:limit].to_i
cmd += %W(-n #{options[:limit].to_i}) offset = options[:offset].to_i
cmd += %w(--format=%H) use_follow_flag = options[:follow] && options[:path].present?
cmd += %W(--skip=#{options[:offset].to_i})
cmd += %w(--follow) if options[:follow] # We will perform the offset in Ruby because --follow doesn't play well with --skip.
cmd += %w(--no-merges) if options[:skip_merges] # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
cmd += %W(--after=#{options[:after].iso8601}) if options[:after] offset_in_ruby = use_follow_flag && options[:offset].present?
cmd += %W(--before=#{options[:before].iso8601}) if options[:before] limit += offset if offset_in_ruby
cmd += [sha]
cmd += %W(-- #{options[:path]}) if options[:path].present? cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
cmd << "--max-count=#{limit}"
raw_output = IO.popen(cmd) {|io| io.read } cmd << '--format=%H'
cmd << "--skip=#{offset}" unless offset_in_ruby
log = raw_output.lines.map do |c| cmd << '--follow' if use_follow_flag
Rugged::Commit.new(rugged, c.strip) cmd << '--no-merges' if options[:skip_merges]
end cmd << "--after=#{options[:after].iso8601}" if options[:after]
cmd << "--before=#{options[:before].iso8601}" if options[:before]
log.is_a?(Array) ? log : [] cmd << sha
cmd += %W[-- #{options[:path]}] if options[:path].present?
raw_output = IO.popen(cmd) { |io| io.read }
lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
end end
def sha_from_ref(ref) def sha_from_ref(ref)
......
...@@ -371,8 +371,14 @@ describe 'Pipelines', :feature, :js do ...@@ -371,8 +371,14 @@ describe 'Pipelines', :feature, :js do
visit new_namespace_project_pipeline_path(project.namespace, project) visit new_namespace_project_pipeline_path(project.namespace, project)
end end
context 'for valid commit' do context 'for valid commit', js: true do
before { fill_in('pipeline[ref]', with: 'master') } before do
click_button project.default_branch
page.within '.dropdown-menu' do
click_link 'master'
end
end
context 'with gitlab-ci.yml' do context 'with gitlab-ci.yml' do
before { stub_ci_pipeline_to_return_yaml_file } before { stub_ci_pipeline_to_return_yaml_file }
...@@ -389,15 +395,6 @@ describe 'Pipelines', :feature, :js do ...@@ -389,15 +395,6 @@ describe 'Pipelines', :feature, :js do
it { expect(page).to have_content('Missing .gitlab-ci.yml file') } it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
end end
end end
context 'for invalid commit' do
before do
fill_in('pipeline[ref]', with: 'invalid-reference')
click_on 'Create pipeline'
end
it { expect(page).to have_content('Reference not found') }
end
end end
describe 'Create pipelines' do describe 'Create pipelines' do
...@@ -409,18 +406,22 @@ describe 'Pipelines', :feature, :js do ...@@ -409,18 +406,22 @@ describe 'Pipelines', :feature, :js do
describe 'new pipeline page' do describe 'new pipeline page' do
it 'has field to add a new pipeline' do it 'has field to add a new pipeline' do
expect(page).to have_field('pipeline[ref]') expect(page).to have_selector('.js-branch-select')
expect(find('.js-branch-select')).to have_content project.default_branch
expect(page).to have_content('Create for') expect(page).to have_content('Create for')
end end
end end
describe 'find pipelines' do describe 'find pipelines' do
it 'shows filtered pipelines', js: true do it 'shows filtered pipelines', js: true do
fill_in('pipeline[ref]', with: 'fix') click_button project.default_branch
find('input#ref').native.send_keys(:keydown)
within('.ui-autocomplete') do page.within '.dropdown-menu' do
expect(page).to have_selector('li', text: 'fix') find('.dropdown-input-field').native.send_keys('fix')
page.within '.dropdown-content' do
expect(page).to have_content('fix')
end
end end
end end
end end
......
require 'spec_helper'
describe 'User Callouts', js: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
before do
login_as(user)
project.team << [user, :master]
end
it 'takes you to the profile preferences when the link is clicked' do
visit dashboard_projects_path
click_link 'Check it out'
expect(current_path).to eq profile_preferences_path
end
describe 'user callout should appear in two routes' do
it 'shows up on the user profile' do
visit user_path(user)
expect(find('.user-callout')).to have_content 'Customize your experience'
end
it 'shows up on the dashboard projects' do
visit dashboard_projects_path
expect(find('.user-callout')).to have_content 'Customize your experience'
end
end
it 'hides the user callout when click on the dismiss icon' do
visit user_path(user)
within('.user-callout') do
find('.close-user-callout').click
end
expect(page).not_to have_selector('#user-callout')
end
end
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
/* global boardsMockInterceptor */ /* global boardsMockInterceptor */
/* global BoardService */ /* global BoardService */
/* global List */ /* global List */
/* global ListIssue */
/* global listObj */ /* global listObj */
/* global listObjDuplicate */
require('~/lib/utils/url_utility'); require('~/lib/utils/url_utility');
require('~/boards/models/issue'); require('~/boards/models/issue');
...@@ -84,4 +86,23 @@ describe('List model', () => { ...@@ -84,4 +86,23 @@ describe('List model', () => {
done(); done();
}, 0); }, 0);
}); });
it('sends service request to update issue label', () => {
const listDup = new List(listObjDuplicate);
const issue = new ListIssue({
title: 'Testing',
iid: 1,
confidential: false,
labels: [list.label, listDup.label]
});
list.issues.push(issue);
listDup.issues.push(issue);
spyOn(gl.boardService, 'moveIssue').and.callThrough();
listDup.updateIssueLabel(list, issue);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(issue.id, list.id, listDup.id);
});
}); });
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
const UserCallout = require('~/user_callout');
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const Cookie = window.Cookies;
describe('UserCallout', () => {
const fixtureName = 'static/user_callout.html.raw';
preloadFixtures(fixtureName);
beforeEach(function () {
loadFixtures(fixtureName);
this.userCallout = new UserCallout();
this.closeButton = $('.close-user-callout');
this.userCalloutBtn = $('.user-callout-btn');
this.userCalloutContainer = $('.user-callout');
Cookie.set(USER_CALLOUT_COOKIE, 'false');
});
afterEach(function () {
Cookie.set(USER_CALLOUT_COOKIE, 'false');
});
it('shows when cookie is set to false', function () {
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined();
expect(this.userCalloutContainer.is(':visible')).toBe(true);
});
it('hides when user clicks on the dismiss-icon', function () {
this.closeButton.click();
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
});
it('hides when user clicks on the "check it out" button', function () {
this.userCalloutBtn.click();
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
});
});
...@@ -529,7 +529,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -529,7 +529,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
commit_with_new_name = nil commit_with_new_name = nil
rename_commit = nil rename_commit = nil
before(:all) do before(:context) do
# Add new commits so that there's a renamed file in the commit history # Add new commits so that there's a renamed file in the commit history
repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
...@@ -538,49 +538,119 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -538,49 +538,119 @@ describe Gitlab::Git::Repository, seed_helper: true do
commit_with_new_name = new_commit_edit_new_file(repo) commit_with_new_name = new_commit_edit_new_file(repo)
end end
after(:context) do
# Erase our commits so other tests get the original repo
repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
context "where 'follow' == true" do context "where 'follow' == true" do
options = { ref: "master", follow: true } let(:options) { { ref: "master", follow: true } }
context "and 'path' is a directory" do context "and 'path' is a directory" do
let(:log_commits) do it "does not follow renames" do
repository.log(options.merge(path: "encoding")) log_commits = repository.log(options.merge(path: "encoding"))
end
it "should not follow renames" do aggregate_failures do
expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name) expect(log_commits).not_to include(commit_with_old_name)
end
end end
end end
context "and 'path' is a file that matches the new filename" do context "and 'path' is a file that matches the new filename" do
let(:log_commits) do context 'without offset' do
repository.log(options.merge(path: "encoding/CHANGELOG")) it "follows renames" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end end
it "should follow renames" do context 'with offset=1' do
expect(log_commits).to include(commit_with_new_name) it "follows renames and skip the latest commit" do
expect(log_commits).to include(rename_commit) log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
expect(log_commits).to include(commit_with_old_name)
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=1', 'and limit=1' do
it "follows renames, skip the latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
expect(log_commits).to contain_exactly(rename_commit)
end
end
context 'with offset=1', 'and limit=2' do
it "follows renames, skip the latest commit and return only two commits" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
aggregate_failures do
expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
end
end
end
context 'with offset=2' do
it "follows renames and skip the latest commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=2', 'and limit=1' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
expect(log_commits).to contain_exactly(commit_with_old_name)
end
end
context 'with offset=2', 'and limit=2' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end end
end end
context "and 'path' is a file that matches the old filename" do context "and 'path' is a file that matches the old filename" do
let(:log_commits) do it "does not follow renames" do
repository.log(options.merge(path: "CHANGELOG")) log_commits = repository.log(options.merge(path: "CHANGELOG"))
end
it "should not follow renames" do aggregate_failures do
expect(log_commits).to include(commit_with_old_name) expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_new_name) expect(log_commits).to include(commit_with_old_name)
end
end end
end end
context "unknown ref" do context "unknown ref" do
let(:log_commits) { repository.log(options.merge(ref: 'unknown')) } it "returns an empty array" do
log_commits = repository.log(options.merge(ref: 'unknown'))
it "should return empty" do
expect(log_commits).to eq([]) expect(log_commits).to eq([])
end end
end end
...@@ -699,12 +769,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -699,12 +769,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
end end
after(:all) do
# Erase our commits so other tests get the original repo
repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
end end
describe "#commits_between" do describe "#commits_between" do
......
...@@ -173,11 +173,11 @@ describe API::Members, api: true do ...@@ -173,11 +173,11 @@ describe API::Members, api: true do
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it 'returns 422 when access_level is not valid' do it 'returns 400 when access_level is not valid' do
post api("/#{source_type.pluralize}/#{source.id}/members", master), post api("/#{source_type.pluralize}/#{source.id}/members", master),
user_id: stranger.id, access_level: 1234 user_id: stranger.id, access_level: 1234
expect(response).to have_http_status(422) expect(response).to have_http_status(400)
end end
end end
end end
...@@ -247,11 +247,11 @@ describe API::Members, api: true do ...@@ -247,11 +247,11 @@ describe API::Members, api: true do
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it 'returns 422 when access level is not valid' do it 'returns 400 when access level is not valid' do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
access_level: 1234 access_level: 1234
expect(response).to have_http_status(422) expect(response).to have_http_status(400)
end end
end end
end end
...@@ -363,7 +363,7 @@ describe API::Members, api: true do ...@@ -363,7 +363,7 @@ describe API::Members, api: true do
post api("/projects/#{project.id}/members", master), post api("/projects/#{project.id}/members", master),
user_id: stranger.id, access_level: Member::OWNER user_id: stranger.id, access_level: Member::OWNER
expect(response).to have_http_status(422) expect(response).to have_http_status(400)
end.to change { project.members.count }.by(0) end.to change { project.members.count }.by(0)
end end
end end
......
require 'spec_helper'
describe API::Runner do
include ApiHelpers
include StubGitlabCalls
let(:registration_token) { 'abcdefg123456' }
before do
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
end
describe '/api/v4/runners' do
describe 'POST /api/v4/runners' do
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners')
expect(response).to have_http_status 400
end
end
context 'when invalid token is provided' do
it 'returns 403 error' do
post api('/runners'), token: 'invalid'
expect(response).to have_http_status 403
end
end
context 'when valid token is provided' do
it 'creates runner with default values' do
post api('/runners'), token: registration_token
runner = Ci::Runner.first
expect(response).to have_http_status 201
expect(json_response['id']).to eq(runner.id)
expect(json_response['token']).to eq(runner.token)
expect(runner.run_untagged).to be true
end
context 'when project token is used' do
let(:project) { create(:empty_project) }
it 'creates runner' do
post api('/runners'), token: project.runners_token
expect(response).to have_http_status 201
expect(project.runners.size).to eq(1)
end
end
end
context 'when runner description is provided' do
it 'creates runner' do
post api('/runners'), token: registration_token,
description: 'server.hostname'
expect(response).to have_http_status 201
expect(Ci::Runner.first.description).to eq('server.hostname')
end
end
context 'when runner tags are provided' do
it 'creates runner' do
post api('/runners'), token: registration_token,
tag_list: 'tag1, tag2'
expect(response).to have_http_status 201
expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
context 'when option for running untagged jobs is provided' do
context 'when tags are provided' do
it 'creates runner' do
post api('/runners'), token: registration_token,
run_untagged: false,
tag_list: ['tag']
expect(response).to have_http_status 201
expect(Ci::Runner.first.run_untagged).to be false
expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
end
end
context 'when tags are not provided' do
it 'returns 404 error' do
post api('/runners'), token: registration_token,
run_untagged: false
expect(response).to have_http_status 404
end
end
end
context 'when option for locking Runner is provided' do
it 'creates runner' do
post api('/runners'), token: registration_token,
locked: true
expect(response).to have_http_status 201
expect(Ci::Runner.first.locked).to be true
end
end
%w(name version revision platform architecture).each do |param|
context "when info parameter '#{param}' info is present" do
let(:value) { "#{param}_value" }
it %q(updates provided Runner's parameter) do
post api('/runners'), token: registration_token,
info: { param => value }
expect(response).to have_http_status 201
expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
end
end
end
end
describe 'DELETE /api/v4/runners' do
context 'when no token is provided' do
it 'returns 400 error' do
delete api('/runners')
expect(response).to have_http_status 400
end
end
context 'when invalid token is provided' do
it 'returns 403 error' do
delete api('/runners'), token: 'invalid'
expect(response).to have_http_status 403
end
end
context 'when valid token is provided' do
let(:runner) { create(:ci_runner) }
it 'deletes Runner' do
delete api('/runners'), token: runner.token
expect(response).to have_http_status 200
expect(Ci::Runner.count).to eq(0)
end
end
end
end
end
...@@ -4410,9 +4410,9 @@ vue-resource@^0.9.3: ...@@ -4410,9 +4410,9 @@ vue-resource@^0.9.3:
version "0.9.3" version "0.9.3"
resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d" resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d"
vue@^2.0.3: vue@^2.1.10:
version "2.0.3" version "2.1.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.0.3.tgz#3f7698f83d6ad1f0e35955447901672876c63fde" resolved "https://registry.yarnpkg.com/vue/-/vue-2.1.10.tgz#c9235ca48c7925137be5807832ac4e3ac180427b"
watchpack@^1.2.0: watchpack@^1.2.0:
version "1.2.1" version "1.2.1"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment