提交 1aad8f2f 编写于 作者: S Sean Packham

Merge branch 'master' into add-university-content

......@@ -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)
- 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)
- 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
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- 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
- 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
- Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc)
- 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.
- Make push events have equal vertical spacing.
- API: Ensure invitees are not returned in Members API.
......
......@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 3.6.0'
gem 'sprockets-es6'
gem 'sprockets', '~> 3.7.0'
gem 'sprockets-es6', '~> 0.9.2'
# Default values for AR models
gem 'default_value_for', '~> 3.0.0'
......@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
gem 'unicorn', '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2'
gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.4'
end
# State machine
......@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2'
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 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
......
......@@ -553,7 +553,7 @@ GEM
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
raindrops (0.15.0)
raindrops (0.17.0)
rake (10.5.0)
rb-fsevent (0.9.6)
rb-inotify (0.9.5)
......@@ -644,7 +644,7 @@ GEM
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.22)
sass-rails (5.0.5)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
......@@ -705,10 +705,10 @@ GEM
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
sprockets (3.6.3)
sprockets (3.7.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-es6 (0.9.0)
sprockets-es6 (0.9.2)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
......@@ -759,9 +759,8 @@ GEM
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.1)
unicorn (4.9.0)
unicorn (5.1.0)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
......@@ -945,7 +944,7 @@ DEPENDENCIES
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
......@@ -964,8 +963,8 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0)
sprockets-es6
sprockets (~> 3.7.0)
sprockets-es6 (~> 0.9.2)
state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2)
......@@ -979,8 +978,8 @@ DEPENDENCIES
uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0)
unf (~> 0.1.4)
unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2)
unicorn (~> 5.1.0)
unicorn-worker-killer (~> 0.4.4)
version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
vmstat (~> 2.2)
......
......@@ -357,7 +357,7 @@
$('ul.emoji-menu-search, h5.emoji-search').remove();
if (term) {
// Generate a search result block
h5 = $('<h5>').text('Search results');
h5 = $('<h5 class="emoji-search" />').text('Search results');
found_emojis = _this.searchEmojis(term).show();
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
......
((w) => {
w.ResolveBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: {
noteId: Number,
discussionId: String,
resolved: Boolean,
namespacePath: String,
projectPath: String,
canResolve: Boolean,
resolvedBy: String
......@@ -69,10 +65,10 @@
if (this.isResolved) {
promise = ResolveService
.unresolve(this.namespace, this.noteId);
.unresolve(this.projectPath, this.noteId);
} else {
promise = ResolveService
.resolve(this.namespace, this.noteId);
.resolve(this.projectPath, this.noteId);
}
promise.then((response) => {
......
((w) => {
w.ResolveDiscussionBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: {
discussionId: String,
mergeRequestId: Number,
namespacePath: String,
projectPath: String,
canResolve: Boolean,
},
......@@ -50,7 +46,7 @@
},
methods: {
resolve: function () {
ResolveService.toggleResolveForDiscussion(this.namespace, this.mergeRequestId, this.discussionId);
ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
}
},
created: function () {
......
((w) => {
w.ButtonMixins = {
computed: {
namespace: function () {
return `${this.namespacePath}/${this.projectPath}`;
}
}
};
})(window);
......@@ -9,32 +9,32 @@
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
}
prepareRequest(namespace) {
prepareRequest(root) {
this.setCSRF();
Vue.http.options.root = `/${namespace}`;
Vue.http.options.root = root;
}
resolve(namespace, noteId) {
this.prepareRequest(namespace);
resolve(projectPath, noteId) {
this.prepareRequest(projectPath);
return this.noteResource.save({ noteId }, {});
}
unresolve(namespace, noteId) {
this.prepareRequest(namespace);
unresolve(projectPath, noteId) {
this.prepareRequest(projectPath);
return this.noteResource.delete({ noteId }, {});
}
toggleResolveForDiscussion(namespace, mergeRequestId, discussionId) {
toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId],
isResolved = discussion.isResolved();
let promise;
if (isResolved) {
promise = this.unResolveAll(namespace, mergeRequestId, discussionId);
promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
} else {
promise = this.resolveAll(namespace, mergeRequestId, discussionId);
promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
}
promise.then((response) => {
......@@ -57,10 +57,10 @@
})
}
resolveAll(namespace, mergeRequestId, discussionId) {
resolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace);
this.prepareRequest(projectPath);
discussion.loading = true;
......@@ -70,10 +70,10 @@
}, {});
}
unResolveAll(namespace, mergeRequestId, discussionId) {
unResolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace);
this.prepareRequest(projectPath);
discussion.loading = true;
......
......@@ -19,7 +19,7 @@
while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) {
values.push(sParameterName[1]);
values.push(sParameterName[1].replace(/\+/g, ' '));
}
i++;
}
......
......@@ -432,14 +432,12 @@
var $form = $(xhr.target);
if ($form.attr('data-resolve-all') != null) {
var namespacePath = $form.attr('data-namespace-path'),
projectPath = $form.attr('data-project-path')
discussionId = $form.attr('data-discussion-id'),
mergeRequestId = $form.attr('data-noteable-iid'),
namespace = namespacePath + '/' + projectPath;
var projectPath = $form.data('project-path')
discussionId = $form.data('discussion-id'),
mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) {
ResolveService.toggleResolveForDiscussion(namespace, mergeRequestId, discussionId);
ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
}
}
......@@ -854,7 +852,6 @@
.closest('form')
.attr('data-discussion-id', discussionId)
.attr('data-resolve-all', 'true')
.attr('data-namespace-path', $this.attr('data-namespace-path'))
.attr('data-project-path', $this.attr('data-project-path'));
};
......
......@@ -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);
......@@ -336,3 +336,9 @@
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 {
}
.no-comments {
opacity: 0.5;
opacity: .5;
}
}
......
......@@ -103,7 +103,7 @@
// Custom dropdown positioning
.dropdown-menu {
top: 30px;
top: 37px;
left: -5px;
padding: 0;
......
......@@ -36,3 +36,7 @@
float: right;
}
}
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
module MembershipActions
extend ActiveSupport::Concern
include MembersHelper
def request_access
membershipable.request_access(current_user)
......@@ -10,11 +9,7 @@ module MembershipActions
end
def approve_access_request
@member = membershipable.requesters.find(params[:id])
return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
@member.accept_request
Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
redirect_to polymorphic_url([membershipable, :members])
end
......
......@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController
:skype,
:twitter,
:username,
:website_url
:website_url,
:organization
)
end
end
......@@ -65,7 +65,7 @@ class UsersController < ApplicationController
format.html { render 'show' }
format.json do
render json: {
html: view_to_html_string("snippets/_snippets", collection: @snippets)
html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true)
}
end
end
......
module LfsHelper
include Gitlab::Routing.url_helpers
def require_lfs_enabled!
return if Gitlab.config.lfs.enabled
render(
json: {
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
)
......@@ -46,7 +48,7 @@ module LfsHelper
render(
json: {
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",
status: 403
......@@ -57,7 +59,7 @@ module LfsHelper
render(
json: {
message: 'Not found.',
documentation_url: "#{Gitlab.config.gitlab.url}/help",
documentation_url: help_url,
},
content_type: "application/vnd.git-lfs+json",
status: 404
......
......@@ -109,7 +109,7 @@ class Notify < BaseMailer
headers['X-GitLab-Reply-Key'] = reply_key
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)
end
......
......@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
validates :project, presence: true
def backlog_list
lists.merge(List.backlog).take
end
def done_list
lists.merge(List.done).take
end
end
......@@ -10,15 +10,33 @@ class CycleAnalytics
end
def commits
repository = @project.repository.raw_repository
if @project.default_branch
repository.log(ref: @project.default_branch, after: @from).count
end
ref = @project.default_branch.presence
count_commits_for(ref)
end
def deploys
@project.deployments.where("created_at > ?", @from).count
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
......@@ -590,13 +590,11 @@ class MergeRequest < ActiveRecord::Base
end
def merge_commit_message
message = "Merge branch '#{source_branch}' into '#{target_branch}'"
message << "\n\n"
message << title.to_s
message << "\n\n"
message << description.to_s
message << "\n\n"
message << "See merge request !#{iid}"
message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
message << "#{title}\n\n"
message << "#{description}\n\n" if description.present?
message << "See merge request #{to_reference}"
message
end
......
......@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService
end
end
def title=(value)
self.properties['title'] = value if self.properties
end
def description
if self.properties && self.properties['description'].present?
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 @@
= render 'dashboard/snippets_head'
.nav-block
.controls
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
.controls.hidden-xs
= link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
= icon('plus')
New Snippet
New snippet
.nav-links.snippet-scope-menu
%li{ class: ("active" unless params[:scope]) }
......@@ -34,5 +34,9 @@
%span.badge
= 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"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable?
......
- if discussion.for_merge_request?
%resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'",
":project-path" => "'#{discussion.project.path}'",
%resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'",
":discussion-id" => "'#{discussion.id}'",
":merge-request-id" => discussion.noteable.iid,
":can-resolve" => discussion.can_resolve?(current_user),
"inline-template" => true }
.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")
{{ buttonText }}
......@@ -6,4 +6,4 @@
= form_tag path do
%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 @@
.row-content-block
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
New Snippet
= link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
New snippet
.oneline
Public snippets created by you and other users are listed here
......
......@@ -2,15 +2,18 @@
- label = 'This group'
- if controller.controller_path =~ /^projects/ && @project.persisted?
- 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?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
- if label.present?
.location-badge= label
.search-input-wrap
.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' }
.dropdown{ data: { url: search_autocomplete_path } }
= 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_content do
%ul
......@@ -21,8 +24,9 @@
%i.search-icon
%i.clear-icon.js-clear-input
= hidden_field_tag :group_id, @group.try(:id)
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_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', class: 'js-search-project-options', data: project_data_attrs
- if @project && @project.persisted?
- if current_controller?(:issues)
......@@ -36,31 +40,6 @@
- else
= 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
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
......
......@@ -86,6 +86,9 @@
.form-group
= f.label :location, 'Location', class: "label-light"
= 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
= f.label :bio, class: "label-light"
= f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
......
......@@ -5,7 +5,7 @@
- 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"}
%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 }}
#notes= render "projects/notes/notes_with_form"
......@@ -24,14 +24,12 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
%resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'",
":project-path" => "'#{note.project.path}'",
":discussion-id" => "'#{note.discussion_id}'",
%resolve-btn{ "project-path" => "#{project_path(note.project)}",
"discussion-id" => "#{note.discussion_id}",
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
":resolved-by" => "'#{note.resolved_by.try(:name)}'",
"resolved-by" => "#{note.resolved_by.try(:name)}",
"v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true,
"v-ref:note_#{note.id}" => true }
......
.hidden-xs
- 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
New Snippet
= 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
- 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
Delete
......@@ -17,8 +17,8 @@
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New Snippet
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
New snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
......
- page_title "Snippets"
.sub-header-block
.pull-right
- if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
New Snippet
- 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
New snippet
.oneline
Share code pastes with others out of git repository
......
......@@ -24,7 +24,7 @@
= succeed '.' do
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
= f.label :commit_message, class: 'control-label'
......
.hidden-xs
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
New Snippet
= link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do
New 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
Delete
......@@ -16,8 +16,8 @@
.dropdown-menu.dropdown-menu-full-width
%ul
%li
= link_to new_snippet_path, title: "New Snippet" do
New Snippet
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= 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
%ul.content-list
= render partial: 'shared/snippets/snippet', collection: @snippets
......@@ -5,7 +7,7 @@
%li
.nothing-here-block Nothing here.
= paginate @snippets, theme: 'gitlab', remote: true
= paginate @snippets, theme: 'gitlab', remote: remote
:javascript
gl.SnippetsList();
......@@ -36,7 +36,7 @@
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
.user-info
.cover-title
= @user.name
......@@ -70,6 +70,10 @@
.profile-link-holder.middle-dot-divider
= icon('map-marker')
= @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
= icon('building')
= @user.organization
- if @user.bio.present?
.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 @@
#
# 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
enable_extension "plpgsql"
......@@ -1132,6 +1132,7 @@ ActiveRecord::Schema.define(version: 20160920160832) do
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false
t.string "organization"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -57,6 +57,7 @@ GET /users
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
......@@ -89,6 +90,7 @@ GET /users
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": "",
"last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1,
......@@ -147,7 +149,8 @@ Parameters:
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": ""
"website_url": "",
"organization": ""
}
```
......@@ -178,6 +181,7 @@ Parameters:
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
......@@ -214,6 +218,7 @@ Parameters:
- `linkedin` (optional) - LinkedIn
- `twitter` (optional) - Twitter account
- `website_url` (optional) - Website URL
- `organization` (optional) - Organization name
- `projects_limit` (optional) - Number of projects user can create
- `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name
......@@ -242,6 +247,7 @@ Parameters:
- `linkedin` - LinkedIn
- `twitter` - Twitter account
- `website_url` - Website URL
- `organization` - Organization name
- `projects_limit` - Limit projects each user can create
- `extern_uid` - External UID
- `provider` - External provider name
......@@ -296,6 +302,7 @@ GET /user
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
......
......@@ -12,7 +12,7 @@ Feature: Project Snippets
And I should not see "Snippet two" in snippets
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"
Then I should see snippet "Snippet three"
......
......@@ -13,6 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_organization', with: 'GitLab'
click_button 'Update profile settings'
@user.reload
end
......@@ -23,6 +24,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
expect(@user.twitter).to eq 'testtwitter'
expect(@user.website_url).to eq 'testurl'
expect(@user.bio).to eq 'I <3 GitLab'
expect(@user.organization).to eq 'GitLab'
expect(find('#user_location').value).to eq 'Ukraine'
end
......
......@@ -21,8 +21,8 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
author: project.users.first)
end
step 'I click link "New Snippet"' do
click_link "New Snippet"
step 'I click link "New snippet"' do
click_link "New snippet"
end
step 'I click link "Snippet one"' do
......
......@@ -55,13 +55,8 @@ module API
put ':id/access_requests/:user_id/approve' do
required_attributes! [:user_id]
source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
member = source.requesters.find_by!(user_id: params[:user_id])
if params[:access_level]
member.update(access_level: params[:access_level])
end
member.accept_request
member = ::Members::ApproveAccessRequestService.new(source, current_user, params).execute
status :created
present member.user, with: Entities::Member, member: member
......
......@@ -28,6 +28,7 @@ module API
helpers ::SentryHelper
helpers ::API::Helpers
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::AwardEmoji
mount ::API::Branches
......@@ -48,6 +49,7 @@ module API
mount ::API::Lint
mount ::API::Members
mount ::API::MergeRequests
mount ::API::MergeRequestDiffs
mount ::API::Milestones
mount ::API::Namespaces
mount ::API::Notes
......@@ -70,6 +72,5 @@ module API
mount ::API::Triggers
mount ::API::Users
mount ::API::Variables
mount ::API::MergeRequestDiffs
end
end
......@@ -15,7 +15,7 @@ module API
class User < UserBasic
expose :created_at
expose :is_admin?, as: :is_admin
expose :bio, :location, :skype, :linkedin, :twitter, :website_url
expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end
class Identity < Grape::Entity
......
......@@ -60,6 +60,7 @@ module API
# linkedin - Linkedin
# twitter - Twitter account
# website_url - Website url
# organization - Organization
# projects_limit - Number of projects user can create
# extern_uid - External authentication provider UID
# provider - External provider
......@@ -74,7 +75,7 @@ module API
post do
authenticated_as_admin!
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)
confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i)
user = User.build_user(attrs)
......@@ -111,6 +112,7 @@ module API
# linkedin - Linkedin
# twitter - Twitter account
# website_url - Website url
# organization - Organization
# projects_limit - Limit projects each user can create
# bio - Bio
# location - Location of the user
......@@ -122,7 +124,7 @@ module API
put ":id" do
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])
not_found!('User') unless user
......
......@@ -73,5 +73,7 @@ excluded_attributes:
methods:
statuses:
- :type
services:
- :type
merge_request_diff:
- :utf8_st_diffs
......@@ -2,6 +2,11 @@
<html>
<head>
<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>
<style>
body {
......@@ -61,4 +66,4 @@
<p>Please contact your GitLab administrator if this problem persists.</p>
</div>
</body>
</html>
</html>
\ No newline at end of file
......@@ -23,7 +23,7 @@ Disallow: /groups/*/edit
Disallow: /users
# Global snippets
Disallow: /s
Disallow: /s/
Disallow: /snippets/new
Disallow: /snippets/*/edit
Disallow: /snippets/*/raw
......
......@@ -2,9 +2,10 @@ require 'spec_helper'
describe Groups::GroupMembersController do
let(:user) { create(:user) }
let(:group) { create(:group) }
describe '#index' do
let(:group) { create(:group) }
before do
group.add_owner(user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
......
......@@ -20,10 +20,7 @@ describe Projects::Boards::ListsController do
end
it 'returns a list of board lists' do
board = project.create_board
create(:backlog_list, board: board)
create(:list, board: board)
create(:done_list, board: board)
read_board_list user: user
......
......@@ -106,6 +106,8 @@ FactoryGirl.define do
factory :project_with_board, parent: :empty_project do
after(:create) do |project|
project.create_board
project.board.lists.create(list_type: :backlog)
project.board.lists.create(list_type: :done)
end
end
end
......@@ -4,15 +4,11 @@ describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
let(:project) { create(:project_with_board, :public) }
let(:user) { create(:user) }
let!(:user2) { create(:user) }
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 << [user2, :master]
......@@ -62,6 +58,7 @@ describe 'Issue Boards', feature: true, js: true do
let(:bug) { create(:label, project: project, name: 'Bug') }
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
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!(:list2) { create(:list, board: project.board, label: development, position: 1) }
......@@ -75,7 +72,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) }
let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) }
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
visit namespace_project_board_path(project.namespace, project)
......@@ -441,6 +438,34 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_empty_boards((2..4))
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
50.times do
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
let(:mail) { ActionMailer::Base.deliveries.last }
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'] }
before do
......
......@@ -3,30 +3,16 @@ require 'spec_helper'
describe 'Snippets tab on a user profile', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
context 'when the user has snippets' do
let(:user) { create(:user) }
let!(:snippets) { create_list(:snippet, 2, :public, author: user) }
before do
create_list(:snippet, 25, :public, author: user)
allow(Snippet).to receive(:default_per_page).and_return(1)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
wait_for_ajax
end
it 'is limited to 20 items per page' do
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
it_behaves_like 'paginated snippets', remote: true
end
end
......@@ -6918,6 +6918,7 @@
"note_events": true,
"build_events": true,
"category": "issue_tracker",
"type": "CustomIssueTrackerService",
"default": true,
"wiki_page_events": true
},
......
......@@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil
end
it 'restores the correct service' do
restored_project_json
expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do
before do
restored_project_json
......
......@@ -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
end
it 'saves the correct service type' do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
it 'has project feature' do
project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty
......@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_id: ci_pipeline.sha)
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(:wiki_access_level, ProjectFeature::ENABLED)
......
......@@ -169,8 +169,9 @@ shared_examples 'it should show Gmail Actions View Commit link' do
end
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', /^<.+>$/
end
it { is_expected.to have_body_text /unsubscribe/ }
......
......@@ -34,6 +34,12 @@ describe CycleAnalytics::Summary, models: true do
expect(subject.commits).to eq(0)
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
describe "#deploys" do
......
......@@ -328,6 +328,42 @@ describe MergeRequest, models: true do
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
let(:merge_if_green) do
create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
......
......@@ -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(:new_issue_url) }
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
......@@ -62,6 +62,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
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 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
......@@ -265,6 +266,14 @@ describe API::API, api: true do
expect(user.reload.bio).to eq('new test bio')
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
put api("/users/#{user.id}", admin), email: user.email
expect(response).to have_http_status(200)
......
......@@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
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!(: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_issue2) { create(:labeled_issue, project: project, labels: [p2]) }
......
......@@ -10,10 +10,10 @@ describe Boards::Issues::MoveService, services: true do
let(:development) { create(:label, project: project, name: 'Development') }
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!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:done) { create(:done_list, board: board) }
let!(:done) { project.board.done_list }
before do
project.team << [user, :developer]
......
......@@ -17,17 +17,15 @@ describe Boards::Lists::CreateService, services: true do
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
create(:backlog_list, board: board)
list = service.execute
expect(list.position).to eq 0
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
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
......@@ -40,8 +38,6 @@ describe Boards::Lists::CreateService, services: true do
context 'when board lists has backlog, label and done 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)
list2 = service.execute
......
......@@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do
end
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)
review = create(:list, board: board, position: 1)
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)
......@@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do
end
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)
expect { service.execute(list) }.not_to change(board.lists, :count)
end
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)
expect { service.execute(list) }.not_to change(board.lists, :count)
......
......@@ -6,12 +6,12 @@ describe Boards::Lists::MoveService, services: true do
let(:board) { project.board }
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!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) }
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
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
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end
def create_commit(message, project, user, branch_name)
filename = random_git_name
def create_commit(message, project, user, branch_name, count: 1)
oldrev = project.repository.commit(branch_name).sha
commit_shas = Array.new(count) do |index|
filename = random_git_name
options = {
committer: project.repository.user_to_committer(user),
author: project.repository.user_to_committer(user),
commit: { message: message, branch: branch_name, update_ref: true },
file: { content: "content", path: filename, update: false }
}
options = {
committer: project.repository.user_to_committer(user),
author: project.repository.user_to_committer(user),
commit: { message: message, branch: branch_name, update_ref: true },
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)
project.repository.commit(commit_sha)
commit_sha
end
GitPushService.new(project,
user,
oldrev: oldrev,
newrev: commit_sha,
newrev: commit_shas.last,
ref: 'refs/heads/master').execute
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% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册