Commit f435bc96 authored by Makoto Scott-Hinkle's avatar Makoto Scott-Hinkle

Merge branch 'master' into 22452-milestone-title-unnecessary-escaping-fix

parents f837238a 3b206ccb
Please view this file on the master branch, on stable branches it's out of date. 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)
- Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Speed-up group milestones show page - Speed-up group milestones show page
- Add more tests for calendar contribution (ClemMakesApps)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Only update issuable labels if they have been changed
- Revoke button in Applications Settings underlines on hover. - Revoke button in Applications Settings underlines on hover.
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile
- Optimize GitHub importing for speed and memory
v 8.12.2 (unreleased) v 8.12.2 (unreleased)
- Fix Import/Export not recognising correctly the imported services. - Fix Import/Export not recognising correctly the imported services.
- Fix snippets pagination - Fix snippets pagination
- Fix List-Unsubscribe header in emails
- Fix IssuesController#show degradation including project on loaded notes
- Fix an issue with the "Commits" section of the cycle analytics summary. !6513
- Fix errors importing project feature and milestone models using GitLab project import
- Make JWT messages Docker-compatible
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
...@@ -115,6 +125,7 @@ v 8.12.0 ...@@ -115,6 +125,7 @@ v 8.12.0
- Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
- Show queued time when showing a pipeline !6084 - Show queued time when showing a pipeline !6084
- Remove unused mixins (ClemMakesApps) - Remove unused mixins (ClemMakesApps)
- Fix issue board label filtering appending already filtered labels
- Add search to all issue board lists - Add search to all issue board lists
- Scroll active tab into view on mobile - Scroll active tab into view on mobile
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
......
...@@ -7,7 +7,7 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -7,7 +7,7 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0' gem 'sprockets', '~> 3.7.0'
gem 'sprockets-es6' gem 'sprockets-es6', '~> 0.9.2'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
...@@ -120,8 +120,8 @@ gem 'diffy', '~> 3.0.3' ...@@ -120,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
...@@ -210,7 +210,7 @@ gem 'oj', '~> 2.17.4' ...@@ -210,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)
...@@ -708,7 +708,7 @@ GEM ...@@ -708,7 +708,7 @@ GEM
sprockets (3.7.0) 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)
...@@ -965,7 +964,7 @@ DEPENDENCIES ...@@ -965,7 +964,7 @@ DEPENDENCIES
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.7.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)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
var _this; var _this;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) { $('.js-label-select').each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected;
$dropdown = $(dropdown); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels'); labelUrl = $dropdown.data('labels');
...@@ -24,6 +24,11 @@ ...@@ -24,6 +24,11 @@
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
initialSelected = $selectbox
.find('input[name="' + $dropdown.data('field-name') + '"]')
.map(function () {
return this.value;
}).get();
if (issueUpdateURL != null) { if (issueUpdateURL != null) {
issueURLSplit = issueUpdateURL.split('/'); issueURLSplit = issueUpdateURL.split('/');
} }
...@@ -43,6 +48,10 @@ ...@@ -43,6 +48,10 @@
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() { selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
return this.value; return this.value;
}).get(); }).get();
if (_.isEqual(initialSelected, selected)) return;
initialSelected = selected;
data = {}; data = {};
data[abilityName] = {}; data[abilityName] = {};
data[abilityName].label_ids = selected; data[abilityName].label_ids = selected;
...@@ -280,12 +289,12 @@ ...@@ -280,12 +289,12 @@
if (page === 'projects:boards:show') { if (page === 'projects:boards:show') {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
} else if (label.title) { } else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
} else { } else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
filters = filters.filter(function (label) { filters = filters.filter(function (filteredLabel) {
return label !== $el.text().trim(); return filteredLabel !== label.title;
}); });
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; gl.issueBoards.BoardsStore.state.filters['label_name'] = filters;
} }
......
...@@ -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);
...@@ -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;
......
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
......
...@@ -25,7 +25,7 @@ class JwtController < ApplicationController ...@@ -25,7 +25,7 @@ class JwtController < ApplicationController
authenticate_with_http_basic do |login, password| authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
render_403 unless @authentication_result.success? && render_unauthorized unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end end
rescue Gitlab::Auth::MissingPersonalTokenError rescue Gitlab::Auth::MissingPersonalTokenError
...@@ -33,10 +33,21 @@ class JwtController < ApplicationController ...@@ -33,10 +33,21 @@ class JwtController < ApplicationController
end end
def render_missing_personal_token def render_missing_personal_token
render plain: "HTTP Basic: Access denied\n" \ render json: {
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ errors: [
"You can generate one at #{profile_personal_access_tokens_url}", { code: 'UNAUTHORIZED',
status: 401 message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}" }
] }, status: 401
end
def render_unauthorized
render json: {
errors: [
{ code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' }
] }, status: 401
end end
def auth_params def auth_params
......
...@@ -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
...@@ -54,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -54,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
raw_notes = @issue.notes_with_associations.fresh raw_notes = @issue.notes.inc_relations_for_view.fresh
@notes = Banzai::NoteRenderer. @notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref) render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
......
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
......
...@@ -7,10 +7,10 @@ module Auth ...@@ -7,10 +7,10 @@ module Auth
def execute(authentication_abilities:) def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities @authentication_abilities = authentication_abilities
return error('not found', 404) unless registry.enabled return error('UNAVAILABLE', status: 404, message: 'registry not enabled') unless registry.enabled
unless current_user || project unless current_user || project
return error('forbidden', 403) unless scope return error('DENIED', status: 403, message: 'access forbidden') unless scope
end end
{ token: authorized_token(scope).encoded } { token: authorized_token(scope).encoded }
...@@ -111,5 +111,12 @@ module Auth ...@@ -111,5 +111,12 @@ module Auth
@authentication_abilities.include?(:create_container_image) && @authentication_abilities.include?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project) can?(current_user, :create_container_image, requested_project)
end end
def error(code, status:, message: '')
{
errors: [{ code: code, message: message }],
http_status: status
}
end
end end
end end
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
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
":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", "v-cloak": true } %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 }}
...@@ -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
......
...@@ -8,13 +8,7 @@ ...@@ -8,13 +8,7 @@
%tbody %tbody
%th Status %th Status
%th Commit %th Commit
- pipelines.stages.each do |stage| %th Stages
%th.stage
- if stage.titleize.length > 12
%span.has-tooltip{ title: "#{stage.titleize}" }
= stage.titleize
- else
= stage.titleize
%th %th
%th %th
= render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, hide_branch: true = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, hide_branch: true
...@@ -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,
......
...@@ -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
......
...@@ -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
......
...@@ -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
......
...@@ -52,7 +52,7 @@ module Gitlab ...@@ -52,7 +52,7 @@ module Gitlab
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
if api.respond_to?(method) if api.respond_to?(method)
request { api.send(method, *args, &block) } request(method, *args, &block)
else else
super(method, *args, &block) super(method, *args, &block)
end end
...@@ -99,20 +99,19 @@ module Gitlab ...@@ -99,20 +99,19 @@ module Gitlab
rate_limit.resets_in + GITHUB_SAFE_SLEEP_TIME rate_limit.resets_in + GITHUB_SAFE_SLEEP_TIME
end end
def request def request(method, *args, &block)
sleep rate_limit_sleep_time if rate_limit_exceed? sleep rate_limit_sleep_time if rate_limit_exceed?
data = yield data = api.send(method, *args, &block)
yield data
last_response = api.last_response last_response = api.last_response
while last_response.rels[:next] while last_response.rels[:next]
sleep rate_limit_sleep_time if rate_limit_exceed? sleep rate_limit_sleep_time if rate_limit_exceed?
last_response = last_response.rels[:next].get last_response = last_response.rels[:next].get
data.concat(last_response.data) if last_response.data.is_a?(Array) yield last_response.data if last_response.data.is_a?(Array)
end end
data
end end
end end
end end
......
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
@repo = project.import_source @repo = project.import_source
@repo_url = project.import_url @repo_url = project.import_url
@errors = [] @errors = []
@labels = {}
if credentials if credentials
@client = Client.new(credentials[:user]) @client = Client.new(credentials[:user])
...@@ -23,6 +24,7 @@ module Gitlab ...@@ -23,6 +24,7 @@ module Gitlab
import_milestones import_milestones
import_issues import_issues
import_pull_requests import_pull_requests
import_comments
import_wiki import_wiki
import_releases import_releases
handle_errors handle_errors
...@@ -46,66 +48,68 @@ module Gitlab ...@@ -46,66 +48,68 @@ module Gitlab
end end
def import_labels def import_labels
labels = client.labels(repo, per_page: 100) client.labels(repo, per_page: 100) do |labels|
labels.each do |raw|
labels.each do |raw| begin
begin label = LabelFormatter.new(project, raw).create!
LabelFormatter.new(project, raw).create! @labels[label.title] = label.id
rescue => e rescue => e
errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end end
end end
end end
def import_milestones def import_milestones
milestones = client.milestones(repo, state: :all, per_page: 100) client.milestones(repo, state: :all, per_page: 100) do |milestones|
milestones.each do |raw|
milestones.each do |raw| begin
begin MilestoneFormatter.new(project, raw).create!
MilestoneFormatter.new(project, raw).create! rescue => e
rescue => e errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } end
end end
end end
end end
def import_issues def import_issues
issues = client.issues(repo, state: :all, sort: :created, direction: :asc, per_page: 100) client.issues(repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |issues|
issues.each do |raw|
issues.each do |raw| gh_issue = IssueFormatter.new(project, raw)
gh_issue = IssueFormatter.new(project, raw)
if gh_issue.valid?
if gh_issue.valid? begin
begin issue = gh_issue.create!
issue = gh_issue.create! apply_labels(issue, raw)
apply_labels(issue) rescue => e
import_comments(issue) if gh_issue.has_comments? errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
rescue => e end
errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end end
end end
end end
end end
def import_pull_requests def import_pull_requests
pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100) client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |pull_requests|
pull_requests = pull_requests.map { |raw| PullRequestFormatter.new(project, raw) }.select(&:valid?) pull_requests.each do |raw|
pull_request = PullRequestFormatter.new(project, raw)
pull_requests.each do |pull_request| next unless pull_request.valid?
begin
restore_source_branch(pull_request) unless pull_request.source_branch_exists? begin
restore_target_branch(pull_request) unless pull_request.target_branch_exists? restore_source_branch(pull_request) unless pull_request.source_branch_exists?
restore_target_branch(pull_request) unless pull_request.target_branch_exists?
merge_request = pull_request.create!
apply_labels(merge_request) merge_request = pull_request.create!
import_comments(merge_request) apply_labels(merge_request, raw)
import_comments_on_diff(merge_request) rescue => e
rescue => e errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message }
errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message } ensure
ensure clean_up_restored_branches(pull_request)
clean_up_restored_branches(pull_request) end
end end
end end
project.repository.after_remove_branch
end end
def restore_source_branch(pull_request) def restore_source_branch(pull_request)
...@@ -125,37 +129,38 @@ module Gitlab ...@@ -125,37 +129,38 @@ module Gitlab
def clean_up_restored_branches(pull_request) def clean_up_restored_branches(pull_request)
remove_branch(pull_request.source_branch_name) unless pull_request.source_branch_exists? remove_branch(pull_request.source_branch_name) unless pull_request.source_branch_exists?
remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists? remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists?
project.repository.after_remove_branch
end end
def apply_labels(issuable) def apply_labels(issuable, raw_issuable)
issue = client.issue(repo, issuable.iid) if raw_issuable.labels.count > 0
label_ids = raw_issuable.labels
if issue.labels.count > 0 .map { |attrs| @labels[attrs.name] }
label_ids = issue.labels
.map { |attrs| project.labels.find_by(title: attrs.name).try(:id) }
.compact .compact
issuable.update_attribute(:label_ids, label_ids) issuable.update_attribute(:label_ids, label_ids)
end end
end end
def import_comments(issuable) def import_comments
comments = client.issue_comments(repo, issuable.iid, per_page: 100) client.issues_comments(repo, per_page: 100) do |comments|
create_comments(issuable, comments) create_comments(comments, :issue)
end end
def import_comments_on_diff(merge_request) client.pull_requests_comments(repo, per_page: 100) do |comments|
comments = client.pull_request_comments(repo, merge_request.iid, per_page: 100) create_comments(comments, :pull_request)
create_comments(merge_request, comments) end
end end
def create_comments(issuable, comments) def create_comments(comments, issuable_type)
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
comments.each do |raw| comments.each do |raw|
begin begin
comment = CommentFormatter.new(project, raw) comment = CommentFormatter.new(project, raw)
issuable_class = issuable_type == :issue ? Issue : MergeRequest
iid = raw.send("#{issuable_type}_url").split('/').last # GH doesn't return parent ID directly
issuable = issuable_class.find_by_iid(iid)
next unless issuable
issuable.notes.create!(comment.attributes) issuable.notes.create!(comment.attributes)
rescue => e rescue => e
errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
...@@ -180,13 +185,14 @@ module Gitlab ...@@ -180,13 +185,14 @@ module Gitlab
end end
def import_releases def import_releases
releases = client.releases(repo, per_page: 100) client.releases(repo, per_page: 100) do |releases|
releases.each do |raw| releases.each do |raw|
begin begin
gh_release = ReleaseFormatter.new(project, raw) gh_release = ReleaseFormatter.new(project, raw)
gh_release.create! if gh_release.valid? gh_release.create! if gh_release.valid?
rescue => e rescue => e
errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end end
end end
end end
......
# Model relationships to be included in the project import/export # Model relationships to be included in the project import/export
project_tree: project_tree:
- :labels
- milestones:
- :events
- issues: - issues:
- :events - :events
- notes: - notes:
...@@ -39,9 +42,6 @@ project_tree: ...@@ -39,9 +42,6 @@ project_tree:
- protected_branches: - protected_branches:
- :merge_access_levels - :merge_access_levels
- :push_access_levels - :push_access_levels
- :labels
- milestones:
- :events
- :project_feature - :project_feature
# Only include the following attributes for the models specified. # Only include the following attributes for the models specified.
......
...@@ -61,11 +61,17 @@ module Gitlab ...@@ -61,11 +61,17 @@ module Gitlab
def restore_project def restore_project
return @project unless @tree_hash return @project unless @tree_hash
project_params = @tree_hash.reject { |_key, value| value.is_a?(Array) }
@project.update(project_params) @project.update(project_params)
@project @project
end end
def project_params
@tree_hash.reject do |key, value|
# return params that are not 1 to many or 1 to 1 relations
value.is_a?(Array) || key == key.singularize
end
end
# Given a relation hash containing one or more models and its relationships, # Given a relation hash containing one or more models and its relationships,
# loops through each model and each object from a model type and # loops through each model and each object from a model type and
# and assigns its correspondent attributes hash from +tree_hash+ # and assigns its correspondent attributes hash from +tree_hash+
......
...@@ -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]
...@@ -470,6 +466,29 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -470,6 +466,29 @@ describe 'Issue Boards', feature: true, js: true do
end end
end end
it 'removes filtered labels' do
wait_for_vue_resource
page.within '.labels-filter' do
click_button('Label')
wait_for_ajax
page.within '.dropdown-menu-labels' do
click_link(testing.title)
wait_for_vue_resource(spinner: false)
end
expect(page).to have_css('input[name="label_name[]"]', visible: false)
page.within '.dropdown-menu-labels' do
click_link(testing.title)
wait_for_vue_resource(spinner: false)
end
expect(page).not_to have_css('input[name="label_name[]"]', visible: false)
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])
......
...@@ -5,13 +5,41 @@ feature 'Contributions Calendar', js: true, feature: true do ...@@ -5,13 +5,41 @@ feature 'Contributions Calendar', js: true, feature: true do
let(:contributed_project) { create(:project, :public) } let(:contributed_project) { create(:project, :public) }
before do date_format = '%A %b %d, %Y'
login_as :user issue_title = 'Bug in old browser'
issue_params = { title: issue_title }
def get_cell_color_selector(contributions)
contribution_cell = '.user-contrib-cell'
activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77']
activity_colors_index = 0
if contributions > 0 && contributions < 10
activity_colors_index = 1
elsif contributions >= 10 && contributions < 20
activity_colors_index = 2
elsif contributions >= 20 && contributions < 30
activity_colors_index = 3
elsif contributions >= 30
activity_colors_index = 4
end
"#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']"
end
def get_cell_date_selector(contributions, date)
contribution_text = 'No contributions'
issue_params = { title: 'Bug in old browser' } if contributions === 1
Issues::CreateService.new(contributed_project, @user, issue_params).execute contribution_text = '1 contribution'
elsif contributions > 1
contribution_text = "#{contributions} contributions"
end
# Push code contribution "#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
end
def push_code_contribution
push_params = { push_params = {
project: contributed_project, project: contributed_project,
action: Event::PUSHED, action: Event::PUSHED,
...@@ -20,7 +48,10 @@ feature 'Contributions Calendar', js: true, feature: true do ...@@ -20,7 +48,10 @@ feature 'Contributions Calendar', js: true, feature: true do
} }
Event.create(push_params) Event.create(push_params)
end
before do
login_as :user
visit @user.username visit @user.username
wait_for_ajax wait_for_ajax
end end
...@@ -29,11 +60,71 @@ feature 'Contributions Calendar', js: true, feature: true do ...@@ -29,11 +60,71 @@ feature 'Contributions Calendar', js: true, feature: true do
expect(page).to have_css('.js-contrib-calendar') expect(page).to have_css('.js-contrib-calendar')
end end
it 'displays calendar activity log', js: true do describe '1 calendar activity' do
expect(find('.content_list .event-note')).to have_content "Bug in old browser" before do
Issues::CreateService.new(contributed_project, @user, issue_params).execute
visit @user.username
wait_for_ajax
end
it 'displays calendar activity log', js: true do
expect(find('.content_list .event-note')).to have_content issue_title
end
it 'displays calendar activity square color for 1 contribution', js: true do
expect(page).to have_selector(get_cell_color_selector(1), count: 1)
end
it 'displays calendar activity square on the correct date', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
end
end
describe '10 calendar activities' do
before do
(0..9).each do |i|
push_code_contribution()
end
visit @user.username
wait_for_ajax
end
it 'displays calendar activity square color for 10 contributions', js: true do
expect(page).to have_selector(get_cell_color_selector(10), count: 1)
end
it 'displays calendar activity square on the correct date', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
end
end end
it 'displays calendar activity square color', js: true do describe 'calendar activity on two days' do
expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1) before do
push_code_contribution()
Timecop.freeze(Date.yesterday)
Issues::CreateService.new(contributed_project, @user, issue_params).execute
Timecop.return
visit @user.username
wait_for_ajax
end
it 'displays calendar activity squares for both days', js: true do
expect(page).to have_selector(get_cell_color_selector(1), count: 2)
end
it 'displays calendar activity square for yesterday', js: true do
yesterday = Date.yesterday.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
end
it 'displays calendar activity square for today', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
end
end end
end end
...@@ -369,6 +369,24 @@ describe 'Issues', feature: true do ...@@ -369,6 +369,24 @@ describe 'Issues', feature: true do
end end
end end
describe 'update labels from issue#show', js: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
let!(:label) { create(:label, project: project) }
before do
visit namespace_project_issue_path(project.namespace, project, issue)
end
it 'will not send ajax request when no data is changed' do
page.within '.labels' do
click_link 'Edit'
first('.dropdown-menu-close').click
expect(page).not_to have_selector('.block-loading')
end
end
end
describe 'update assignee from issue#show' do describe 'update assignee from issue#show' do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
......
...@@ -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
......
...@@ -48,9 +48,9 @@ ...@@ -48,9 +48,9 @@
setTimeout(() => { setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10); expect($('.dropdown-content a').length).toBe(10);
$('.dropdow-content a').each((i, $link) => { $('.dropdown-content a').each(function (i) {
if (i < 5) { if (i < saveLabelCount) {
$link.get(0).click(); $(this).get(0).click();
} }
}); });
...@@ -70,9 +70,9 @@ ...@@ -70,9 +70,9 @@
setTimeout(() => { setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10); expect($('.dropdown-content a').length).toBe(10);
$('.dropdow-content a').each((i, $link) => { $('.dropdown-content a').each(function (i) {
if (i < 5) { if (i < saveLabelCount) {
$link.get(0).click(); $(this).get(0).click();
} }
}); });
...@@ -86,4 +86,3 @@ ...@@ -86,4 +86,3 @@
}); });
}); });
})(); })();
...@@ -66,6 +66,6 @@ describe Gitlab::GithubImport::Client, lib: true do ...@@ -66,6 +66,6 @@ describe Gitlab::GithubImport::Client, lib: true do
stub_request(:get, /api.github.com/) stub_request(:get, /api.github.com/)
allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound) allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound)
expect { client.issues }.not_to raise_error expect { client.issues {} }.not_to raise_error
end end
end end
...@@ -57,7 +57,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -57,7 +57,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347' url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347',
labels: [double(name: 'Label #1')],
) )
end end
...@@ -75,7 +76,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -75,7 +76,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348' url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348',
labels: [double(name: 'Label #2')],
) )
end end
...@@ -94,7 +96,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -94,7 +96,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
merged_at: nil, merged_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347',
labels: [double(name: 'Label #3')],
) )
end end
...@@ -129,6 +132,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -129,6 +132,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone]) allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2]) allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request]) allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil })) allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2]) allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error) allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
...@@ -148,9 +153,7 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -148,9 +153,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
errors: [ errors: [
{ type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" }, { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
{ type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
{ type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" },
{ type: :wiki, errors: "Gitlab::Shell::Error" }, { type: :wiki, errors: "Gitlab::Shell::Error" },
{ type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" } { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
......
...@@ -2231,6 +2231,31 @@ ...@@ -2231,6 +2231,31 @@
], ],
"milestones": [ "milestones": [
{
"id": 1,
"title": "test milestone",
"project_id": 8,
"description": "test milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"events": [
{
"id": 487,
"target_type": "Milestone",
"target_id": 1,
"title": null,
"data": null,
"project_id": 46,
"created_at": "2016-06-14T15:02:04.418Z",
"updated_at": "2016-06-14T15:02:04.418Z",
"action": 1,
"author_id": 18
}
]
},
{ {
"id": 20, "id": 20,
"title": "v4.0", "title": "v4.0",
...@@ -7373,5 +7398,16 @@ ...@@ -7373,5 +7398,16 @@
} }
] ]
} }
] ],
"project_feature": {
"builds_access_level": 0,
"created_at": "2014-12-26T09:26:45.000Z",
"id": 2,
"issues_access_level": 0,
"merge_requests_access_level": 20,
"project_id": 4,
"snippets_access_level": 20,
"updated_at": "2016-09-23T11:58:28.000Z",
"wiki_access_level": 20
}
} }
\ No newline at end of file
...@@ -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 'has a project feature' do
restored_project_json
expect(project.project_feature).not_to be_nil
end
it 'restores the correct service' do it 'restores the correct service' do
restored_project_json restored_project_json
......
...@@ -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),
......
...@@ -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)
......
...@@ -39,7 +39,7 @@ describe JwtController do ...@@ -39,7 +39,7 @@ describe JwtController do
subject! { get '/jwt/auth', parameters, headers } subject! { get '/jwt/auth', parameters, headers }
it { expect(response).to have_http_status(403) } it { expect(response).to have_http_status(401) }
end end
end end
...@@ -77,7 +77,7 @@ describe JwtController do ...@@ -77,7 +77,7 @@ describe JwtController do
subject! { get '/jwt/auth', parameters, headers } subject! { get '/jwt/auth', parameters, headers }
it { expect(response).to have_http_status(403) } it { expect(response).to have_http_status(401) }
end end
end end
......
...@@ -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
......
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