Commit 1aad8f2f authored by Sean Packham's avatar Sean Packham

Merge branch 'master' into add-university-content

parents 0ef54d50 7b42ff63
...@@ -2,13 +2,21 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,13 +2,21 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased) v 8.13.0 (unreleased)
- Speed-up group milestones show page - Speed-up group milestones show page
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Revoke button in Applications Settings underlines on hover.
- Add organization field to user profile
v 8.12.2 (unreleased) v 8.12.2 (unreleased)
- Added University content to doc/university - Added University content to doc/university
- Fix Import/Export not recognising correctly the imported services.
- Fix snippets pagination
- Fix List-Unsubscribe header in emails
- Fix an issue with the "Commits" section of the cycle analytics summary. !6513
v 8.12.1 v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- Fix issue with search filter labels not displaying - Fix issue with search filter labels not displaying
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
v 8.12.0 v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
...@@ -24,6 +32,7 @@ v 8.12.0 ...@@ -24,6 +32,7 @@ v 8.12.0
- Filter tags by name !6121 - Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
- Fix resolve discussion buttons endpoint path
- Fix note form hint showing slash commands supported for commits. - Fix note form hint showing slash commands supported for commits.
- Make push events have equal vertical spacing. - Make push events have equal vertical spacing.
- API: Ensure invitees are not returned in Members API. - API: Ensure invitees are not returned in Members API.
......
...@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance gem 'sprockets', '~> 3.7.0'
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 gem 'sprockets-es6', '~> 0.9.2'
gem 'sprockets', '~> 3.6.0'
gem 'sprockets-es6'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
...@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3' ...@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem 'unicorn', '~> 4.9.0' gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.4'
end end
# State machine # State machine
...@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4' ...@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
gem 'sass-rails', '~> 5.0.0' gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0' gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
......
...@@ -553,7 +553,7 @@ GEM ...@@ -553,7 +553,7 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.1.0) rainbow (2.1.0)
raindrops (0.15.0) raindrops (0.17.0)
rake (10.5.0) rake (10.5.0)
rb-fsevent (0.9.6) rb-fsevent (0.9.6)
rb-inotify (0.9.5) rb-inotify (0.9.5)
...@@ -644,7 +644,7 @@ GEM ...@@ -644,7 +644,7 @@ GEM
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.22) sass (3.4.22)
sass-rails (5.0.5) sass-rails (5.0.6)
railties (>= 4.0.0, < 6) railties (>= 4.0.0, < 6)
sass (~> 3.1) sass (~> 3.1)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
...@@ -705,10 +705,10 @@ GEM ...@@ -705,10 +705,10 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (3.6.3) sprockets (3.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-es6 (0.9.0) sprockets-es6 (0.9.2)
babel-source (>= 5.8.11) babel-source (>= 5.8.11)
babel-transpiler babel-transpiler
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
...@@ -759,9 +759,8 @@ GEM ...@@ -759,9 +759,8 @@ GEM
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.1) unicode-display_width (1.1.1)
unicorn (4.9.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
rack
raindrops (~> 0.7) raindrops (~> 0.7)
unicorn-worker-killer (0.4.4) unicorn-worker-killer (0.4.4)
get_process_mem (~> 0) get_process_mem (~> 0)
...@@ -945,7 +944,7 @@ DEPENDENCIES ...@@ -945,7 +944,7 @@ DEPENDENCIES
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9) ruby-prof (~> 0.15.9)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0) scss_lint (~> 0.47.0)
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
...@@ -964,8 +963,8 @@ DEPENDENCIES ...@@ -964,8 +963,8 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0) sprockets (~> 3.7.0)
sprockets-es6 sprockets-es6 (~> 0.9.2)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2) task_list (~> 1.0.2)
...@@ -979,8 +978,8 @@ DEPENDENCIES ...@@ -979,8 +978,8 @@ DEPENDENCIES
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.9.0) unicorn (~> 5.1.0)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.4)
version_sorter (~> 2.1.0) version_sorter (~> 2.1.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.2) vmstat (~> 2.2)
......
...@@ -357,7 +357,7 @@ ...@@ -357,7 +357,7 @@
$('ul.emoji-menu-search, h5.emoji-search').remove(); $('ul.emoji-menu-search, h5.emoji-search').remove();
if (term) { if (term) {
// Generate a search result block // Generate a search result block
h5 = $('<h5>').text('Search results'); h5 = $('<h5 class="emoji-search" />').text('Search results');
found_emojis = _this.searchEmojis(term).show(); found_emojis = _this.searchEmojis(term).show();
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis); ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide(); $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
......
((w) => { ((w) => {
w.ResolveBtn = Vue.extend({ w.ResolveBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: { props: {
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
resolved: Boolean, resolved: Boolean,
namespacePath: String,
projectPath: String, projectPath: String,
canResolve: Boolean, canResolve: Boolean,
resolvedBy: String resolvedBy: String
...@@ -69,10 +65,10 @@ ...@@ -69,10 +65,10 @@
if (this.isResolved) { if (this.isResolved) {
promise = ResolveService promise = ResolveService
.unresolve(this.namespace, this.noteId); .unresolve(this.projectPath, this.noteId);
} else { } else {
promise = ResolveService promise = ResolveService
.resolve(this.namespace, this.noteId); .resolve(this.projectPath, this.noteId);
} }
promise.then((response) => { promise.then((response) => {
......
((w) => { ((w) => {
w.ResolveDiscussionBtn = Vue.extend({ w.ResolveDiscussionBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
namespacePath: String,
projectPath: String, projectPath: String,
canResolve: Boolean, canResolve: Boolean,
}, },
...@@ -50,7 +46,7 @@ ...@@ -50,7 +46,7 @@
}, },
methods: { methods: {
resolve: function () { resolve: function () {
ResolveService.toggleResolveForDiscussion(this.namespace, this.mergeRequestId, this.discussionId); ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
} }
}, },
created: function () { created: function () {
......
((w) => {
w.ButtonMixins = {
computed: {
namespace: function () {
return `${this.namespacePath}/${this.projectPath}`;
}
}
};
})(window);
...@@ -9,32 +9,32 @@ ...@@ -9,32 +9,32 @@
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken(); Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
} }
prepareRequest(namespace) { prepareRequest(root) {
this.setCSRF(); this.setCSRF();
Vue.http.options.root = `/${namespace}`; Vue.http.options.root = root;
} }
resolve(namespace, noteId) { resolve(projectPath, noteId) {
this.prepareRequest(namespace); this.prepareRequest(projectPath);
return this.noteResource.save({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
unresolve(namespace, noteId) { unresolve(projectPath, noteId) {
this.prepareRequest(namespace); this.prepareRequest(projectPath);
return this.noteResource.delete({ noteId }, {}); return this.noteResource.delete({ noteId }, {});
} }
toggleResolveForDiscussion(namespace, mergeRequestId, discussionId) { toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId], const discussion = CommentsStore.state[discussionId],
isResolved = discussion.isResolved(); isResolved = discussion.isResolved();
let promise; let promise;
if (isResolved) { if (isResolved) {
promise = this.unResolveAll(namespace, mergeRequestId, discussionId); promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
} else { } else {
promise = this.resolveAll(namespace, mergeRequestId, discussionId); promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
} }
promise.then((response) => { promise.then((response) => {
...@@ -57,10 +57,10 @@ ...@@ -57,10 +57,10 @@
}) })
} }
resolveAll(namespace, mergeRequestId, discussionId) { resolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace); this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
...@@ -70,10 +70,10 @@ ...@@ -70,10 +70,10 @@
}, {}); }, {});
} }
unResolveAll(namespace, mergeRequestId, discussionId) { unResolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace); this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
while (i < sURLVariables.length) { while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('='); sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) { if (sParameterName[0] === sParam) {
values.push(sParameterName[1]); values.push(sParameterName[1].replace(/\+/g, ' '));
} }
i++; i++;
} }
......
...@@ -432,14 +432,12 @@ ...@@ -432,14 +432,12 @@
var $form = $(xhr.target); var $form = $(xhr.target);
if ($form.attr('data-resolve-all') != null) { if ($form.attr('data-resolve-all') != null) {
var namespacePath = $form.attr('data-namespace-path'), var projectPath = $form.data('project-path')
projectPath = $form.attr('data-project-path') discussionId = $form.data('discussion-id'),
discussionId = $form.attr('data-discussion-id'), mergeRequestId = $form.data('noteable-iid');
mergeRequestId = $form.attr('data-noteable-iid'),
namespace = namespacePath + '/' + projectPath;
if (ResolveService != null) { if (ResolveService != null) {
ResolveService.toggleResolveForDiscussion(namespace, mergeRequestId, discussionId); ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
} }
} }
...@@ -854,7 +852,6 @@ ...@@ -854,7 +852,6 @@
.closest('form') .closest('form')
.attr('data-discussion-id', discussionId) .attr('data-discussion-id', discussionId)
.attr('data-resolve-all', 'true') .attr('data-resolve-all', 'true')
.attr('data-namespace-path', $this.attr('data-namespace-path'))
.attr('data-project-path', $this.attr('data-project-path')); .attr('data-project-path', $this.attr('data-project-path'));
}; };
......
...@@ -389,4 +389,41 @@ ...@@ -389,4 +389,41 @@
})(); })();
$(function() {
var $projectOptionsDataEl = $('.js-search-project-options');
var $groupOptionsDataEl = $('.js-search-group-options');
var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
if ($projectOptionsDataEl.length) {
gl.projectOptions = gl.projectOptions || {};
var projectPath = $projectOptionsDataEl.data('project-path');
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
mrPath: $projectOptionsDataEl.data('mr-path')
};
}
if ($groupOptionsDataEl.length) {
gl.groupOptions = gl.groupOptions || {};
var groupPath = $groupOptionsDataEl.data('group-path');
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'),
mrPath: $groupOptionsDataEl.data('mr-path')
};
}
if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
mrPath: $dashboardOptionsDataEl.data('mr-path')
};
}
});
}).call(this); }).call(this);
...@@ -336,3 +336,9 @@ ...@@ -336,3 +336,9 @@
box-shadow: inset 0 0 0 white; box-shadow: inset 0 0 0 white;
} }
} }
@media (max-width: $screen-xs-max) {
.btn-wide-on-xs {
width: 100%;
}
}
...@@ -164,7 +164,7 @@ ul.content-list { ...@@ -164,7 +164,7 @@ ul.content-list {
} }
.no-comments { .no-comments {
opacity: 0.5; opacity: .5;
} }
} }
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
// Custom dropdown positioning // Custom dropdown positioning
.dropdown-menu { .dropdown-menu {
top: 30px; top: 37px;
left: -5px; left: -5px;
padding: 0; padding: 0;
......
...@@ -36,3 +36,7 @@ ...@@ -36,3 +36,7 @@
float: right; float: right;
} }
} }
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
module MembershipActions module MembershipActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
include MembersHelper
def request_access def request_access
membershipable.request_access(current_user) membershipable.request_access(current_user)
...@@ -10,11 +9,7 @@ module MembershipActions ...@@ -10,11 +9,7 @@ module MembershipActions
end end
def approve_access_request def approve_access_request
@member = membershipable.requesters.find(params[:id]) Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
@member.accept_request
redirect_to polymorphic_url([membershipable, :members]) redirect_to polymorphic_url([membershipable, :members])
end end
......
...@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController ...@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController
:skype, :skype,
:twitter, :twitter,
:username, :username,
:website_url :website_url,
:organization
) )
end end
end end
...@@ -65,7 +65,7 @@ class UsersController < ApplicationController ...@@ -65,7 +65,7 @@ class UsersController < ApplicationController
format.html { render 'show' } format.html { render 'show' }
format.json do format.json do
render json: { render json: {
html: view_to_html_string("snippets/_snippets", collection: @snippets) html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true)
} }
end end
end end
......
module LfsHelper module LfsHelper
include Gitlab::Routing.url_helpers
def require_lfs_enabled! def require_lfs_enabled!
return if Gitlab.config.lfs.enabled return if Gitlab.config.lfs.enabled
render( render(
json: { json: {
message: 'Git LFS is not enabled on this GitLab server, contact your admin.', message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
status: 501 status: 501
) )
...@@ -46,7 +48,7 @@ module LfsHelper ...@@ -46,7 +48,7 @@ module LfsHelper
render( render(
json: { json: {
message: 'Access forbidden. Check your access level.', message: 'Access forbidden. Check your access level.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
status: 403 status: 403
...@@ -57,7 +59,7 @@ module LfsHelper ...@@ -57,7 +59,7 @@ module LfsHelper
render( render(
json: { json: {
message: 'Not found.', message: 'Not found.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
status: 404 status: 404
......
...@@ -109,7 +109,7 @@ class Notify < BaseMailer ...@@ -109,7 +109,7 @@ class Notify < BaseMailer
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
if !@labels_url && @sent_notification && @sent_notification.unsubscribable? if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true) headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end end
......
...@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base ...@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
validates :project, presence: true validates :project, presence: true
def backlog_list
lists.merge(List.backlog).take
end
def done_list
lists.merge(List.done).take
end
end end
...@@ -10,15 +10,33 @@ class CycleAnalytics ...@@ -10,15 +10,33 @@ class CycleAnalytics
end end
def commits def commits
repository = @project.repository.raw_repository ref = @project.default_branch.presence
count_commits_for(ref)
if @project.default_branch
repository.log(ref: @project.default_branch, after: @from).count
end
end end
def deploys def deploys
@project.deployments.where("created_at > ?", @from).count @project.deployments.where("created_at > ?", @from).count
end end
private
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
# the easiest way forward is to replicate the relevant portions of the
# `log` function here.
def count_commits_for(ref)
return unless ref
repository = @project.repository.raw_repository
sha = @project.repository.commit(ref).sha
cmd = %W(git --git-dir=#{repository.path} log)
cmd << '--format=%H'
cmd << "--after=#{@from.iso8601}"
cmd << sha
raw_output = IO.popen(cmd) { |io| io.read }
raw_output.lines.count
end
end end
end end
...@@ -590,13 +590,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -590,13 +590,11 @@ class MergeRequest < ActiveRecord::Base
end end
def merge_commit_message def merge_commit_message
message = "Merge branch '#{source_branch}' into '#{target_branch}'" message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
message << "\n\n" message << "#{title}\n\n"
message << title.to_s message << "#{description}\n\n" if description.present?
message << "\n\n" message << "See merge request #{to_reference}"
message << description.to_s
message << "\n\n"
message << "See merge request !#{iid}"
message message
end end
......
...@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService ...@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService
end end
end end
def title=(value)
self.properties['title'] = value if self.properties
end
def description def description
if self.properties && self.properties['description'].present? if self.properties && self.properties['description'].present?
self.properties['description'] self.properties['description']
......
module Members
class ApproveAccessRequestService < BaseService
include MembersHelper
attr_accessor :source
def initialize(source, current_user, params = {})
@source = source
@current_user = current_user
@params = params
end
def execute
condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
access_requester = source.requesters.find_by!(condition)
raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester)
access_requester.access_level = params[:access_level] if params[:access_level]
access_requester.accept_request
access_requester
end
private
def can_update_access_requester?(access_requester)
access_requester && can?(current_user, action_member_permission(:update, access_requester), access_requester)
end
end
end
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
= render 'dashboard/snippets_head' = render 'dashboard/snippets_head'
.nav-block .nav-block
.controls .controls.hidden-xs
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
= icon('plus') = icon('plus')
New Snippet New snippet
.nav-links.snippet-scope-menu .nav-links.snippet-scope-menu
%li{ class: ("active" unless params[:scope]) } %li{ class: ("active" unless params[:scope]) }
...@@ -34,5 +34,9 @@ ...@@ -34,5 +34,9 @@
%span.badge %span.badge
= current_user.snippets.are_public.count = current_user.snippets.are_public.count
= render 'snippets/snippets' .visible-xs
= link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do
= icon('plus')
New snippet
= render 'snippets/snippets'
= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
......
- if discussion.for_merge_request? - if discussion.for_merge_request?
%resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'", %resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'",
":project-path" => "'#{discussion.project.path}'",
":discussion-id" => "'#{discussion.id}'", ":discussion-id" => "'#{discussion.id}'",
":merge-request-id" => discussion.noteable.iid, ":merge-request-id" => discussion.noteable.iid,
":can-resolve" => discussion.can_resolve?(current_user), ":can-resolve" => discussion.can_resolve?(current_user),
"inline-template" => true } "inline-template" => true }
.btn-group{ role: "group", "v-if" => "showButton" } .btn-group{ role: "group", "v-if" => "showButton" }
%button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading" } %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading", "v-cloak": true }
= icon("spinner spin", "v-show" => "loading") = icon("spinner spin", "v-show" => "loading")
{{ buttonText }} {{ buttonText }}
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
= form_tag path do = form_tag path do
%input{:name => "_method", :type => "hidden", :value => "delete"}/ %input{:name => "_method", :type => "hidden", :value => "delete"}/
= submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm' = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm'
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
.row-content-block .row-content-block
- if current_user - if current_user
.pull-right = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do New snippet
New Snippet
.oneline .oneline
Public snippets created by you and other users are listed here Public snippets created by you and other users are listed here
......
...@@ -2,15 +2,18 @@ ...@@ -2,15 +2,18 @@
- label = 'This group' - label = 'This group'
- if controller.controller_path =~ /^projects/ && @project.persisted? - if controller.controller_path =~ /^projects/ && @project.persisted?
- label = 'This project' - label = 'This project'
- if @group && @group.persisted? && @group.path
- group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
- if @project && @project.persisted?
- project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) }
.search.search-form{class: "#{'has-location-badge' if label.present?}"} .search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f| = form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container .search-input-container
- if label.present? - if label.present?
.location-badge= label .location-badge= label
.search-input-wrap .search-input-wrap
.dropdown{ data: {url: search_autocomplete_path } } .dropdown{ data: { url: search_autocomplete_path } }
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' } = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
= dropdown_content do = dropdown_content do
%ul %ul
...@@ -21,8 +24,9 @@ ...@@ -21,8 +24,9 @@
%i.search-icon %i.search-icon
%i.clear-icon.js-clear-input %i.clear-icon.js-clear-input
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: project_data_attrs
- if @project && @project.persisted? - if @project && @project.persisted?
- if current_controller?(:issues) - if current_controller?(:issues)
...@@ -36,31 +40,6 @@ ...@@ -36,31 +40,6 @@
- else - else
= hidden_field_tag :search_code, true = hidden_field_tag :search_code, true
:javascript
gl.projectOptions = gl.projectOptions || {};
gl.projectOptions["#{j(@project.path)}"] = {
issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}",
mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}",
name: "#{j(@project.name)}"
};
- if @group && @group.persisted? && @group.path
:javascript
gl.groupOptions = gl.groupOptions || {};
gl.groupOptions["#{j(@group.path)}"] = {
name: "#{j(@group.name)}",
issuesPath: "#{issues_group_path(j(@group.path))}",
mrPath: "#{merge_requests_group_path(j(@group.path))}"
};
:javascript
gl.dashboardOptions = {
issuesPath: "#{issues_dashboard_url}",
mrPath: "#{merge_requests_dashboard_url}"
};
- if @snippet || @snippets - if @snippet || @snippets
= hidden_field_tag :snippets, true = hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref = hidden_field_tag :repository_ref, @ref
......
...@@ -86,6 +86,9 @@ ...@@ -86,6 +86,9 @@
.form-group .form-group
= f.label :location, 'Location', class: "label-light" = f.label :location, 'Location', class: "label-light"
= f.text_field :location, class: "form-control" = f.text_field :location, class: "form-control"
.form-group
= f.label :organization, 'Organization', class: "label-light"
= f.text_field :organization, class: "form-control"
.form-group .form-group
= f.label :bio, class: "label-light" = f.label :bio, class: "label-light"
= f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- if @merge_request.reopenable? - if @merge_request.reopenable?
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
%comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
%button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
{{ buttonText }} {{ buttonText }}
#notes= render "projects/notes/notes_with_form" #notes= render "projects/notes/notes_with_form"
...@@ -24,14 +24,12 @@ ...@@ -24,14 +24,12 @@
- if note.resolvable? - if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note) - can_resolve = can?(current_user, :resolve_note, note)
%resolve-btn{ "project-path" => "#{project_path(note.project)}",
%resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'", "discussion-id" => "#{note.discussion_id}",
":project-path" => "'#{note.project.path}'",
":discussion-id" => "'#{note.discussion_id}'",
":note-id" => note.id, ":note-id" => note.id,
":resolved" => note.resolved?, ":resolved" => note.resolved?,
":can-resolve" => can_resolve, ":can-resolve" => can_resolve,
":resolved-by" => "'#{note.resolved_by.try(:name)}'", "resolved-by" => "#{note.resolved_by.try(:name)}",
"v-show" => "#{can_resolve || note.resolved?}", "v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true, "inline-template" => true,
"v-ref:note_#{note.id}" => true } "v-ref:note_#{note.id}" => true }
......
.hidden-xs .hidden-xs
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do
New Snippet New snippet
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete Delete
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
%ul %ul
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
New Snippet New snippet
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
%li %li
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
......
- page_title "Snippets" - page_title "Snippets"
.sub-header-block .sub-header-block
.pull-right - if can?(current_user, :create_project_snippet, @project)
- if can?(current_user, :create_project_snippet, @project) = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do New snippet
New Snippet
.oneline .oneline
Share code pastes with others out of git repository Share code pastes with others out of git repository
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= succeed '.' do = succeed '.' do
More examples are in the More examples are in the
= link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown") = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
.form-group .form-group
= f.label :commit_message, class: 'control-label' = f.label :commit_message, class: 'control-label'
......
.hidden-xs .hidden-xs
- if current_user - if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do
New Snippet New snippet
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete Delete
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
%li %li
= link_to new_snippet_path, title: "New Snippet" do = link_to new_snippet_path, title: "New snippet" do
New Snippet New snippet
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
......
- remote = local_assigns.fetch(:remote, false)
.snippets-list-holder .snippets-list-holder
%ul.content-list %ul.content-list
= render partial: 'shared/snippets/snippet', collection: @snippets = render partial: 'shared/snippets/snippet', collection: @snippets
...@@ -5,7 +7,7 @@ ...@@ -5,7 +7,7 @@
%li %li
.nothing-here-block Nothing here. .nothing-here-block Nothing here.
= paginate @snippets, theme: 'gitlab', remote: true = paginate @snippets, theme: 'gitlab', remote: remote
:javascript :javascript
gl.SnippetsList(); gl.SnippetsList();
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
.avatar-holder .avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do = link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
.user-info .user-info
.cover-title .cover-title
= @user.name = @user.name
...@@ -70,6 +70,10 @@ ...@@ -70,6 +70,10 @@
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= icon('map-marker') = icon('map-marker')
= @user.location = @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
= icon('building')
= @user.organization
- if @user.bio.present? - if @user.bio.present?
.cover-desc .cover-desc
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddOrganizationToUser < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :users, :organization, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160920160832) do ActiveRecord::Schema.define(version: 20160926145521) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1132,6 +1132,7 @@ ActiveRecord::Schema.define(version: 20160920160832) do ...@@ -1132,6 +1132,7 @@ ActiveRecord::Schema.define(version: 20160920160832) do
t.datetime "otp_grace_period_started_at" t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false t.boolean "external", default: false
t.string "organization"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -57,6 +57,7 @@ GET /users ...@@ -57,6 +57,7 @@ GET /users
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
...@@ -89,6 +90,7 @@ GET /users ...@@ -89,6 +90,7 @@ GET /users
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "",
"last_sign_in_at": null, "last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z", "confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1, "theme_id": 1,
...@@ -147,7 +149,8 @@ Parameters: ...@@ -147,7 +149,8 @@ Parameters:
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "" "website_url": "",
"organization": ""
} }
``` ```
...@@ -178,6 +181,7 @@ Parameters: ...@@ -178,6 +181,7 @@ Parameters:
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
...@@ -214,6 +218,7 @@ Parameters: ...@@ -214,6 +218,7 @@ Parameters:
- `linkedin` (optional) - LinkedIn - `linkedin` (optional) - LinkedIn
- `twitter` (optional) - Twitter account - `twitter` (optional) - Twitter account
- `website_url` (optional) - Website URL - `website_url` (optional) - Website URL
- `organization` (optional) - Organization name
- `projects_limit` (optional) - Number of projects user can create - `projects_limit` (optional) - Number of projects user can create
- `extern_uid` (optional) - External UID - `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name - `provider` (optional) - External provider name
...@@ -242,6 +247,7 @@ Parameters: ...@@ -242,6 +247,7 @@ Parameters:
- `linkedin` - LinkedIn - `linkedin` - LinkedIn
- `twitter` - Twitter account - `twitter` - Twitter account
- `website_url` - Website URL - `website_url` - Website URL
- `organization` - Organization name
- `projects_limit` - Limit projects each user can create - `projects_limit` - Limit projects each user can create
- `extern_uid` - External UID - `extern_uid` - External UID
- `provider` - External provider name - `provider` - External provider name
...@@ -296,6 +302,7 @@ GET /user ...@@ -296,6 +302,7 @@ GET /user
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
......
...@@ -12,7 +12,7 @@ Feature: Project Snippets ...@@ -12,7 +12,7 @@ Feature: Project Snippets
And I should not see "Snippet two" in snippets And I should not see "Snippet two" in snippets
Scenario: I create new project snippet Scenario: I create new project snippet
Given I click link "New Snippet" Given I click link "New snippet"
And I submit new snippet "Snippet three" And I submit new snippet "Snippet three"
Then I should see snippet "Snippet three" Then I should see snippet "Snippet three"
......
...@@ -13,6 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -13,6 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in 'user_website_url', with: 'testurl' fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine' fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab' fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_organization', with: 'GitLab'
click_button 'Update profile settings' click_button 'Update profile settings'
@user.reload @user.reload
end end
...@@ -23,6 +24,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -23,6 +24,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
expect(@user.twitter).to eq 'testtwitter' expect(@user.twitter).to eq 'testtwitter'
expect(@user.website_url).to eq 'testurl' expect(@user.website_url).to eq 'testurl'
expect(@user.bio).to eq 'I <3 GitLab' expect(@user.bio).to eq 'I <3 GitLab'
expect(@user.organization).to eq 'GitLab'
expect(find('#user_location').value).to eq 'Ukraine' expect(find('#user_location').value).to eq 'Ukraine'
end end
......
...@@ -21,8 +21,8 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps ...@@ -21,8 +21,8 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'I click link "New Snippet"' do step 'I click link "New snippet"' do
click_link "New Snippet" click_link "New snippet"
end end
step 'I click link "Snippet one"' do step 'I click link "Snippet one"' do
......
...@@ -55,13 +55,8 @@ module API ...@@ -55,13 +55,8 @@ module API
put ':id/access_requests/:user_id/approve' do put ':id/access_requests/:user_id/approve' do
required_attributes! [:user_id] required_attributes! [:user_id]
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
member = source.requesters.find_by!(user_id: params[:user_id]) member = ::Members::ApproveAccessRequestService.new(source, current_user, params).execute
if params[:access_level]
member.update(access_level: params[:access_level])
end
member.accept_request
status :created status :created
present member.user, with: Entities::Member, member: member present member.user, with: Entities::Member, member: member
......
...@@ -28,6 +28,7 @@ module API ...@@ -28,6 +28,7 @@ module API
helpers ::SentryHelper helpers ::SentryHelper
helpers ::API::Helpers helpers ::API::Helpers
# Keep in alphabetical order
mount ::API::AccessRequests mount ::API::AccessRequests
mount ::API::AwardEmoji mount ::API::AwardEmoji
mount ::API::Branches mount ::API::Branches
...@@ -48,6 +49,7 @@ module API ...@@ -48,6 +49,7 @@ module API
mount ::API::Lint mount ::API::Lint
mount ::API::Members mount ::API::Members
mount ::API::MergeRequests mount ::API::MergeRequests
mount ::API::MergeRequestDiffs
mount ::API::Milestones mount ::API::Milestones
mount ::API::Namespaces mount ::API::Namespaces
mount ::API::Notes mount ::API::Notes
...@@ -70,6 +72,5 @@ module API ...@@ -70,6 +72,5 @@ module API
mount ::API::Triggers mount ::API::Triggers
mount ::API::Users mount ::API::Users
mount ::API::Variables mount ::API::Variables
mount ::API::MergeRequestDiffs
end end
end end
...@@ -15,7 +15,7 @@ module API ...@@ -15,7 +15,7 @@ module API
class User < UserBasic class User < UserBasic
expose :created_at expose :created_at
expose :is_admin?, as: :is_admin expose :is_admin?, as: :is_admin
expose :bio, :location, :skype, :linkedin, :twitter, :website_url expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end end
class Identity < Grape::Entity class Identity < Grape::Entity
......
...@@ -60,6 +60,7 @@ module API ...@@ -60,6 +60,7 @@ module API
# linkedin - Linkedin # linkedin - Linkedin
# twitter - Twitter account # twitter - Twitter account
# website_url - Website url # website_url - Website url
# organization - Organization
# projects_limit - Number of projects user can create # projects_limit - Number of projects user can create
# extern_uid - External authentication provider UID # extern_uid - External authentication provider UID
# provider - External provider # provider - External provider
...@@ -74,7 +75,7 @@ module API ...@@ -74,7 +75,7 @@ module API
post do post do
authenticated_as_admin! authenticated_as_admin!
required_attributes! [:email, :password, :name, :username] required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external, :organization]
admin = attrs.delete(:admin) admin = attrs.delete(:admin)
confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i) confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i)
user = User.build_user(attrs) user = User.build_user(attrs)
...@@ -111,6 +112,7 @@ module API ...@@ -111,6 +112,7 @@ module API
# linkedin - Linkedin # linkedin - Linkedin
# twitter - Twitter account # twitter - Twitter account
# website_url - Website url # website_url - Website url
# organization - Organization
# projects_limit - Limit projects each user can create # projects_limit - Limit projects each user can create
# bio - Bio # bio - Bio
# location - Location of the user # location - Location of the user
...@@ -122,7 +124,7 @@ module API ...@@ -122,7 +124,7 @@ module API
put ":id" do put ":id" do
authenticated_as_admin! authenticated_as_admin!
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external, :organization]
user = User.find(params[:id]) user = User.find(params[:id])
not_found!('User') unless user not_found!('User') unless user
......
...@@ -73,5 +73,7 @@ excluded_attributes: ...@@ -73,5 +73,7 @@ excluded_attributes:
methods: methods:
statuses: statuses:
- :type - :type
services:
- :type
merge_request_diff: merge_request_diff:
- :utf8_st_diffs - :utf8_st_diffs
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
<html> <html>
<head> <head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"> <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta name="refresh" content="60">
<meta name="retry-after" content="100">
<meta name="robots" content="noindex, nofollow, noarchive, nostore">
<meta name="cache-control" content="no-cache, no-store">
<meta name="pragma" content="no-cache">
<title>Deploy in progress</title> <title>Deploy in progress</title>
<style> <style>
body { body {
...@@ -61,4 +66,4 @@ ...@@ -61,4 +66,4 @@
<p>Please contact your GitLab administrator if this problem persists.</p> <p>Please contact your GitLab administrator if this problem persists.</p>
</div> </div>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -23,7 +23,7 @@ Disallow: /groups/*/edit ...@@ -23,7 +23,7 @@ Disallow: /groups/*/edit
Disallow: /users Disallow: /users
# Global snippets # Global snippets
Disallow: /s Disallow: /s/
Disallow: /snippets/new Disallow: /snippets/new
Disallow: /snippets/*/edit Disallow: /snippets/*/edit
Disallow: /snippets/*/raw Disallow: /snippets/*/raw
......
...@@ -2,9 +2,10 @@ require 'spec_helper' ...@@ -2,9 +2,10 @@ require 'spec_helper'
describe Groups::GroupMembersController do describe Groups::GroupMembersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) }
describe '#index' do describe '#index' do
let(:group) { create(:group) }
before do before do
group.add_owner(user) group.add_owner(user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
......
...@@ -20,10 +20,7 @@ describe Projects::Boards::ListsController do ...@@ -20,10 +20,7 @@ describe Projects::Boards::ListsController do
end end
it 'returns a list of board lists' do it 'returns a list of board lists' do
board = project.create_board
create(:backlog_list, board: board)
create(:list, board: board) create(:list, board: board)
create(:done_list, board: board)
read_board_list user: user read_board_list user: user
......
...@@ -106,6 +106,8 @@ FactoryGirl.define do ...@@ -106,6 +106,8 @@ FactoryGirl.define do
factory :project_with_board, parent: :empty_project do factory :project_with_board, parent: :empty_project do
after(:create) do |project| after(:create) do |project|
project.create_board project.create_board
project.board.lists.create(list_type: :backlog)
project.board.lists.create(list_type: :done)
end end
end end
end end
...@@ -4,15 +4,11 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -4,15 +4,11 @@ describe 'Issue Boards', feature: true, js: true do
include WaitForAjax include WaitForAjax
include WaitForVueResource include WaitForVueResource
let(:project) { create(:empty_project, :public) } let(:project) { create(:project_with_board, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:user2) { create(:user) } let!(:user2) { create(:user) }
before do before do
project.create_board
project.board.lists.create(list_type: :backlog)
project.board.lists.create(list_type: :done)
project.team << [user, :master] project.team << [user, :master]
project.team << [user2, :master] project.team << [user2, :master]
...@@ -62,6 +58,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -62,6 +58,7 @@ describe 'Issue Boards', feature: true, js: true do
let(:bug) { create(:label, project: project, name: 'Bug') } let(:bug) { create(:label, project: project, name: 'Bug') }
let!(:backlog) { create(:label, project: project, name: 'Backlog') } let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:done) { create(:label, project: project, name: 'Done') } let!(:done) { create(:label, project: project, name: 'Done') }
let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } let!(:list1) { create(:list, board: project.board, label: planning, position: 0) }
let!(:list2) { create(:list, board: project.board, label: development, position: 1) } let!(:list2) { create(:list, board: project.board, label: development, position: 1) }
...@@ -75,7 +72,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -75,7 +72,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) } let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) }
let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) } let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) }
let!(:issue8) { create(:closed_issue, project: project) } let!(:issue8) { create(:closed_issue, project: project) }
let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug]) } let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) }
before do before do
visit namespace_project_board_path(project.namespace, project) visit namespace_project_board_path(project.namespace, project)
...@@ -441,6 +438,34 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -441,6 +438,34 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_empty_boards((2..4)) wait_for_empty_boards((2..4))
end end
it 'filters by label with space after reload' do
page.within '.issues-filters' do
click_button('Label')
wait_for_ajax
page.within '.dropdown-menu-labels' do
click_link(accepting.title)
wait_for_vue_resource(spinner: false)
find('.dropdown-menu-close').click
end
end
# Test after reload
page.evaluate_script 'window.location.reload()'
wait_for_vue_resource
page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('1')
expect(page).to have_selector('.card', count: 1)
end
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('0')
expect(page).to have_selector('.card', count: 0)
end
end
it 'infinite scrolls list with label filter' do it 'infinite scrolls list with label filter' do
50.times do 50.times do
create(:labeled_issue, project: project, labels: [testing]) create(:labeled_issue, project: project, labels: [testing])
......
require 'spec_helper'
describe 'Dashboard snippets', feature: true do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
login_as(project.owner)
visit dashboard_snippets_path
end
it_behaves_like 'paginated snippets'
end
end
require 'spec_helper'
describe 'Project snippets', feature: true do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
visit namespace_project_snippets_path(project.namespace, project)
end
it_behaves_like 'paginated snippets'
end
end
require 'spec_helper'
describe 'Snippets', feature: true do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
visit snippets_path(username: project.owner.username)
end
it_behaves_like 'paginated snippets'
end
end
...@@ -11,7 +11,7 @@ describe 'Unsubscribe links', feature: true do ...@@ -11,7 +11,7 @@ describe 'Unsubscribe links', feature: true do
let(:mail) { ActionMailer::Base.deliveries.last } let(:mail) { ActionMailer::Base.deliveries.last }
let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) } let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
let(:header_link) { mail.header['List-Unsubscribe'] } let(:header_link) { mail.header['List-Unsubscribe'].to_s[1..-2] } # Strip angle brackets
let(:body_link) { body.find_link('unsubscribe')['href'] } let(:body_link) { body.find_link('unsubscribe')['href'] }
before do before do
......
...@@ -3,30 +3,16 @@ require 'spec_helper' ...@@ -3,30 +3,16 @@ require 'spec_helper'
describe 'Snippets tab on a user profile', feature: true, js: true do describe 'Snippets tab on a user profile', feature: true, js: true do
include WaitForAjax include WaitForAjax
let(:user) { create(:user) }
context 'when the user has snippets' do context 'when the user has snippets' do
let(:user) { create(:user) }
let!(:snippets) { create_list(:snippet, 2, :public, author: user) }
before do before do
create_list(:snippet, 25, :public, author: user) allow(Snippet).to receive(:default_per_page).and_return(1)
visit user_path(user) visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' } page.within('.user-profile-nav') { click_link 'Snippets' }
wait_for_ajax wait_for_ajax
end end
it 'is limited to 20 items per page' do it_behaves_like 'paginated snippets', remote: true
expect(page.all('.snippets-list-holder .snippet-row').count).to eq(20)
end
context 'clicking on the link to the second page' do
before do
click_link('2')
wait_for_ajax
end
it 'shows the remaining snippets' do
expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5)
end
end
end end
end end
...@@ -6918,6 +6918,7 @@ ...@@ -6918,6 +6918,7 @@
"note_events": true, "note_events": true,
"build_events": true, "build_events": true,
"category": "issue_tracker", "category": "issue_tracker",
"type": "CustomIssueTrackerService",
"default": true, "default": true,
"wiki_page_events": true "wiki_page_events": true
}, },
......
...@@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do ...@@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil expect(Label.first.label_links.first.target).not_to be_nil
end end
it 'restores the correct service' do
restored_project_json
expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do context 'Merge requests' do
before do before do
restored_project_json restored_project_json
......
...@@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end end
it 'saves the correct service type' do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
it 'has project feature' do it 'has project feature' do
project_feature = saved_project_json['project_feature'] project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty expect(project_feature).not_to be_empty
...@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_id: ci_pipeline.sha) commit_id: ci_pipeline.sha)
create(:event, target: milestone, project: project, action: Event::CREATED, author: user) create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
......
...@@ -169,8 +169,9 @@ shared_examples 'it should show Gmail Actions View Commit link' do ...@@ -169,8 +169,9 @@ shared_examples 'it should show Gmail Actions View Commit link' do
end end
shared_examples 'an unsubscribeable thread' do shared_examples 'an unsubscribeable thread' do
it 'has a List-Unsubscribe header' do it 'has a List-Unsubscribe header in the correct format' do
is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
is_expected.to have_header 'List-Unsubscribe', /^<.+>$/
end end
it { is_expected.to have_body_text /unsubscribe/ } it { is_expected.to have_body_text /unsubscribe/ }
......
...@@ -34,6 +34,12 @@ describe CycleAnalytics::Summary, models: true do ...@@ -34,6 +34,12 @@ describe CycleAnalytics::Summary, models: true do
expect(subject.commits).to eq(0) expect(subject.commits).to eq(0)
end end
it "finds a large (> 100) snumber of commits if present" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) }
expect(subject.commits).to eq(100)
end
end end
describe "#deploys" do describe "#deploys" do
......
...@@ -328,6 +328,42 @@ describe MergeRequest, models: true do ...@@ -328,6 +328,42 @@ describe MergeRequest, models: true do
end end
end end
describe '#merge_commit_message' do
it 'includes merge information as the title' do
request = build(:merge_request, source_branch: 'source', target_branch: 'target')
expect(request.merge_commit_message)
.to match("Merge branch 'source' into 'target'\n\n")
end
it 'includes its title in the body' do
request = build(:merge_request, title: 'Remove all technical debt')
expect(request.merge_commit_message)
.to match("Remove all technical debt\n\n")
end
it 'includes its description in the body' do
request = build(:merge_request, description: 'By removing all code')
expect(request.merge_commit_message)
.to match("By removing all code\n\n")
end
it 'includes its reference in the body' do
request = build_stubbed(:merge_request)
expect(request.merge_commit_message)
.to match("See merge request #{request.to_reference}")
end
it 'excludes multiple linebreak runs when description is blank' do
request = build(:merge_request, title: 'Title', description: nil)
expect(request.merge_commit_message).not_to match("Title\n\n\n\n")
end
end
describe "#reset_merge_when_build_succeeds" do describe "#reset_merge_when_build_succeeds" do
let(:merge_if_green) do let(:merge_if_green) do
create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user), create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
......
...@@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do ...@@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do
it { is_expected.not_to validate_presence_of(:issues_url) } it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) } it { is_expected.not_to validate_presence_of(:new_issue_url) }
end end
context 'title' do
let(:issue_tracker) { described_class.new(properties: {}) }
it 'sets a default title' do
issue_tracker.title = nil
expect(issue_tracker.title).to eq('Custom Issue Tracker')
end
it 'sets the custom title' do
issue_tracker.title = 'test title'
expect(issue_tracker.title).to eq('test title')
end
end
end end
end end
...@@ -62,6 +62,7 @@ describe API::API, api: true do ...@@ -62,6 +62,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first.keys).to include 'email' expect(json_response.first.keys).to include 'email'
expect(json_response.first.keys).to include 'organization'
expect(json_response.first.keys).to include 'identities' expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project' expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled' expect(json_response.first.keys).to include 'two_factor_enabled'
...@@ -265,6 +266,14 @@ describe API::API, api: true do ...@@ -265,6 +266,14 @@ describe API::API, api: true do
expect(user.reload.bio).to eq('new test bio') expect(user.reload.bio).to eq('new test bio')
end end
it "updates user with organization" do
put api("/users/#{user.id}", admin), { organization: 'GitLab' }
expect(response).to have_http_status(200)
expect(json_response['organization']).to eq('GitLab')
expect(user.reload.organization).to eq('GitLab')
end
it 'updates user with his own email' do it 'updates user with his own email' do
put api("/users/#{user.id}", admin), email: user.email put api("/users/#{user.id}", admin), email: user.email
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
......
...@@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do ...@@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
let!(:backlog) { create(:backlog_list, board: board) } let!(:backlog) { project.board.backlog_list }
let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) } let!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:done) { create(:done_list, board: board) } let!(:done) { project.board.done_list }
let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) }
let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) }
......
...@@ -10,10 +10,10 @@ describe Boards::Issues::MoveService, services: true do ...@@ -10,10 +10,10 @@ describe Boards::Issues::MoveService, services: true do
let(:development) { create(:label, project: project, name: 'Development') } let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') } let(:testing) { create(:label, project: project, name: 'Testing') }
let!(:backlog) { create(:backlog_list, board: board) } let!(:backlog) { project.board.backlog_list }
let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) } let!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:done) { create(:done_list, board: board) } let!(:done) { project.board.done_list }
before do before do
project.team << [user, :developer] project.team << [user, :developer]
......
...@@ -17,17 +17,15 @@ describe Boards::Lists::CreateService, services: true do ...@@ -17,17 +17,15 @@ describe Boards::Lists::CreateService, services: true do
end end
end end
context 'when board lists has only a backlog list' do context 'when board lists has backlog, and done lists' do
it 'creates a new list at beginning of the list' do it 'creates a new list at beginning of the list' do
create(:backlog_list, board: board)
list = service.execute list = service.execute
expect(list.position).to eq 0 expect(list.position).to eq 0
end end
end end
context 'when board lists has only labels lists' do context 'when board lists has labels lists' do
it 'creates a new list at end of the lists' do it 'creates a new list at end of the lists' do
create(:list, board: board, position: 0) create(:list, board: board, position: 0)
create(:list, board: board, position: 1) create(:list, board: board, position: 1)
...@@ -40,8 +38,6 @@ describe Boards::Lists::CreateService, services: true do ...@@ -40,8 +38,6 @@ describe Boards::Lists::CreateService, services: true do
context 'when board lists has backlog, label and done lists' do context 'when board lists has backlog, label and done lists' do
it 'creates a new list at end of the label lists' do it 'creates a new list at end of the label lists' do
create(:backlog_list, board: board)
create(:done_list, board: board)
list1 = create(:list, board: board, position: 0) list1 = create(:list, board: board, position: 0)
list2 = service.execute list2 = service.execute
......
...@@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do ...@@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do
end end
it 'decrements position of higher lists' do it 'decrements position of higher lists' do
backlog = create(:backlog_list, board: board) backlog = project.board.backlog_list
development = create(:list, board: board, position: 0) development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1) review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2) staging = create(:list, board: board, position: 2)
done = create(:done_list, board: board) done = project.board.done_list
described_class.new(project, user).execute(development) described_class.new(project, user).execute(development)
...@@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do ...@@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do
end end
it 'does not remove list from board when list type is backlog' do it 'does not remove list from board when list type is backlog' do
list = create(:backlog_list, board: board) list = project.board.backlog_list
service = described_class.new(project, user) service = described_class.new(project, user)
expect { service.execute(list) }.not_to change(board.lists, :count) expect { service.execute(list) }.not_to change(board.lists, :count)
end end
it 'does not remove list from board when list type is done' do it 'does not remove list from board when list type is done' do
list = create(:done_list, board: board) list = project.board.done_list
service = described_class.new(project, user) service = described_class.new(project, user)
expect { service.execute(list) }.not_to change(board.lists, :count) expect { service.execute(list) }.not_to change(board.lists, :count)
......
...@@ -6,12 +6,12 @@ describe Boards::Lists::MoveService, services: true do ...@@ -6,12 +6,12 @@ describe Boards::Lists::MoveService, services: true do
let(:board) { project.board } let(:board) { project.board }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:backlog) { create(:backlog_list, board: board) } let!(:backlog) { project.board.backlog_list }
let!(:planning) { create(:list, board: board, position: 0) } let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) } let!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) } let!(:review) { create(:list, board: board, position: 2) }
let!(:staging) { create(:list, board: board, position: 3) } let!(:staging) { create(:list, board: board, position: 3) }
let!(:done) { create(:done_list, board: board) } let!(:done) { project.board.done_list }
context 'when list type is set to label' do context 'when list type is set to label' do
it 'keeps position of lists when new position is nil' do it 'keeps position of lists when new position is nil' do
......
require 'spec_helper'
describe Members::ApproveAccessRequestService, services: true do
let(:user) { create(:user) }
let(:access_requester) { create(:user) }
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
shared_examples 'a service raising ActiveRecord::RecordNotFound' do
it 'raises ActiveRecord::RecordNotFound' do
expect { described_class.new(source, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
expect { described_class.new(source, user, params).execute }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
shared_examples 'a service approving an access request' do
it 'succeeds' do
expect { described_class.new(source, user, params).execute }.to change { source.requesters.count }.by(-1)
end
it 'returns a <Source>Member' do
member = described_class.new(source, user, params).execute
expect(member).to be_a "#{source.class.to_s}Member".constantize
expect(member.requested_at).to be_nil
end
context 'with a custom access level' do
let(:params) { { user_id: access_requester.id, access_level: Gitlab::Access::MASTER } }
it 'returns a ProjectMember with the custom access level' do
member = described_class.new(source, user, params).execute
expect(member.access_level).to eq Gitlab::Access::MASTER
end
end
end
context 'when no access requester are found' do
let(:params) { { user_id: 42 } }
it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do
let(:source) { project }
end
it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do
let(:source) { group }
end
end
context 'when an access requester is found' do
before do
project.request_access(access_requester)
group.request_access(access_requester)
end
let(:params) { { user_id: access_requester.id } }
context 'when current user cannot approve access request to the project' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:source) { project }
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:source) { group }
end
end
context 'when current user can approve access request to the project' do
before do
project.team << [user, :master]
group.add_owner(user)
end
it_behaves_like 'a service approving an access request' do
let(:source) { project }
end
it_behaves_like 'a service approving an access request' do
let(:source) { group }
end
context 'when given a :id' do
let(:params) { { id: project.requesters.find_by!(user_id: access_requester.id).id } }
it_behaves_like 'a service approving an access request' do
let(:source) { project }
end
end
end
end
end
...@@ -4,24 +4,28 @@ module CycleAnalyticsHelpers ...@@ -4,24 +4,28 @@ module CycleAnalyticsHelpers
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end end
def create_commit(message, project, user, branch_name) def create_commit(message, project, user, branch_name, count: 1)
filename = random_git_name
oldrev = project.repository.commit(branch_name).sha oldrev = project.repository.commit(branch_name).sha
commit_shas = Array.new(count) do |index|
filename = random_git_name
options = { options = {
committer: project.repository.user_to_committer(user), committer: project.repository.user_to_committer(user),
author: project.repository.user_to_committer(user), author: project.repository.user_to_committer(user),
commit: { message: message, branch: branch_name, update_ref: true }, commit: { message: message, branch: branch_name, update_ref: true },
file: { content: "content", path: filename, update: false } file: { content: "content", path: filename, update: false }
} }
commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
project.repository.commit(commit_sha)
commit_sha = Gitlab::Git::Blob.commit(project.repository, options) commit_sha
project.repository.commit(commit_sha) end
GitPushService.new(project, GitPushService.new(project,
user, user,
oldrev: oldrev, oldrev: oldrev,
newrev: commit_sha, newrev: commit_shas.last,
ref: 'refs/heads/master').execute ref: 'refs/heads/master').execute
end end
......
# These shared examples expect a `snippets` array of snippets
RSpec.shared_examples 'paginated snippets' do |remote: false|
it "is limited to #{Snippet.default_per_page} items per page" do
expect(page.all('.snippets-list-holder .snippet-row').count).to eq(Snippet.default_per_page)
end
context 'clicking on the link to the second page' do
before do
click_link('2')
wait_for_ajax if remote
end
it 'shows the remaining snippets' do
remaining_snippets_count = [snippets.size - Snippet.default_per_page, Snippet.default_per_page].min
expect(page).to have_selector('.snippets-list-holder .snippet-row', count: remaining_snippets_count)
end
end
end
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