Commit 8100e98b authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-01-31' into 'master'

CE upstream - 2018-01-31 15:29 UTC

Closes gitaly#979 et gitaly#967

See merge request gitlab-org/gitlab-ee!4330
parents c25232f9 b67070f9
--- ---
engines: engines:
brakeman:
enabled: true
bundler-audit: bundler-audit:
enabled: true enabled: true
duplication: duplication:
......
...@@ -26,7 +26,11 @@ const Api = { ...@@ -26,7 +26,11 @@ const Api = {
const url = Api.buildUrl(Api.groupPath) const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId); .replace(':id', groupId);
return axios.get(url) return axios.get(url)
.then(({ data }) => callback(data)); .then(({ data }) => {
callback(data);
return data;
});
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
......
import { getLocationHash } from './url_utility'; import { getLocationHash } from './url_utility';
import axios from './axios_utils';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
...@@ -382,22 +383,16 @@ export const resetFavicon = () => { ...@@ -382,22 +383,16 @@ export const resetFavicon = () => {
} }
}; };
export const setCiStatusFavicon = (pageUrl) => { export const setCiStatusFavicon = pageUrl =>
$.ajax({ axios.get(pageUrl)
url: pageUrl, .then(({ data }) => {
dataType: 'json',
success: (data) => {
if (data && data.favicon) { if (data && data.favicon) {
setFavicon(data.favicon); setFavicon(data.favicon);
} else { } else {
resetFavicon(); resetFavicon();
} }
}, })
error: () => { .catch(resetFavicon);
resetFavicon();
},
});
};
export const spriteIcon = (icon, className = '') => { export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : ''; const classAttribute = className.length > 0 ? `class="${className}"` : '';
......
...@@ -157,7 +157,6 @@ class GroupsController < Groups::ApplicationController ...@@ -157,7 +157,6 @@ class GroupsController < Groups::ApplicationController
@projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user) @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute .execute
.includes(:namespace) .includes(:namespace)
.page(params[:page])
@events = EventCollection @events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter) .new(@projects, offset: params[:offset].to_i, filter: event_filter)
......
...@@ -901,7 +901,7 @@ class Repository ...@@ -901,7 +901,7 @@ class Repository
@root_ref_sha ||= commit(root_ref).sha @root_ref_sha ||= commit(root_ref).sha
end end
delegate :merged_branch_names, to: :raw_repository delegate :merged_branch_names, :can_be_merged?, to: :raw_repository
def merge_base(first_commit_id, second_commit_id) def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
...@@ -1020,7 +1020,7 @@ class Repository ...@@ -1020,7 +1020,7 @@ class Repository
end end
instance_variable_set(ivar, value) instance_variable_set(ivar, value)
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository rescue Gitlab::Git::Repository::NoRepository
# Even if the above `#exists?` check passes these errors might still # Even if the above `#exists?` check passes these errors might still
# occur (for example because of a non-existing HEAD). We want to # occur (for example because of a non-existing HEAD). We want to
# gracefully handle this and not cache anything # gracefully handle this and not cache anything
......
...@@ -158,7 +158,6 @@ ...@@ -158,7 +158,6 @@
GitLab Pages GitLab Pages
%span.pull-right %span.pull-right
= Gitlab::Pages::VERSION = Gitlab::Pages::VERSION
- if Gitlab::Geo.enabled? - if Gitlab::Geo.enabled?
%p %p
= link_to 'Geo', admin_geo_nodes_path = link_to 'Geo', admin_geo_nodes_path
...@@ -167,7 +166,6 @@ ...@@ -167,7 +166,6 @@
= Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node' = Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node'
- else - else
Undefined Undefined
%p %p
Ruby Ruby
%span.pull-right %span.pull-right
......
---
title: Fix not all events being shown in group dashboard
merge_request:
author:
type: fixed
---
title: Remove N+1 queries with /projects/:project_id/{access_requests,members} API
endpoints
merge_request:
author:
type: performance
raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5 raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
require 'active_record/connection_adapters/postgresql_adapter' if Gitlab::Database.postgresql?
require 'active_record/connection_adapters/postgresql/schema_statements' require 'active_record/connection_adapters/postgresql_adapter'
require 'active_record/connection_adapters/postgresql/schema_statements'
#
# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330 #
# # Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
# Updates sequence logic to support PostgreSQL 10. #
# # Updates sequence logic to support PostgreSQL 10.
# rubocop:disable all #
module ActiveRecord # rubocop:disable all
module ConnectionAdapters module ActiveRecord
module ConnectionAdapters
# We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
# to work. In ActiveRecord 4, it is protected. # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
# https://github.com/mbleigh/seed-fu/issues/123 # to work. In ActiveRecord 4, it is protected.
class PostgreSQLAdapter # https://github.com/mbleigh/seed-fu/issues/123
public :postgresql_version class PostgreSQLAdapter
end public :postgresql_version
end
module PostgreSQL module PostgreSQL
module SchemaStatements module SchemaStatements
# Resets the sequence of a table's primary key to the maximum value. # Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
unless pk and sequence unless pk and sequence
default_pk, default_sequence = pk_and_sequence_for(table) default_pk, default_sequence = pk_and_sequence_for(table)
pk ||= default_pk pk ||= default_pk
sequence ||= default_sequence sequence ||= default_sequence
end end
if @logger && pk && !sequence if @logger && pk && !sequence
@logger.warn "#{table} has primary key #{pk} with no default sequence" @logger.warn "#{table} has primary key #{pk} with no default sequence"
end end
if pk && sequence if pk && sequence
quoted_sequence = quote_table_name(sequence) quoted_sequence = quote_table_name(sequence)
max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}") max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
if max_pk.nil? if max_pk.nil?
if postgresql_version >= 100000 if postgresql_version >= 100000
minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass") minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
else else
minvalue = select_value("SELECT min_value FROM #{quoted_sequence}") minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
end
end end
end
select_value <<-end_sql, 'SCHEMA' select_value <<-end_sql, 'SCHEMA'
SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false}) SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
end_sql end_sql
end
end end
end end
end end
end end
end end
# rubocop:enable all
end end
# rubocop:enable all
...@@ -7,10 +7,12 @@ if Gitlab::Database.mysql? ...@@ -7,10 +7,12 @@ if Gitlab::Database.mysql?
require 'peek-mysql2' require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client PEEK_DB_CLIENT = ::Mysql2::Client
PEEK_DB_VIEW = Peek::Views::Mysql2 PEEK_DB_VIEW = Peek::Views::Mysql2
else elsif Gitlab::Database.postgresql?
require 'peek-pg' require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG PEEK_DB_VIEW = Peek::Views::PG
else
raise "Unsupported database adapter for peek!"
end end
Peek.into PEEK_DB_VIEW Peek.into PEEK_DB_VIEW
......
...@@ -114,6 +114,7 @@ Parameters: ...@@ -114,6 +114,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified - `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
- `format` (optional) - The archive format. Default is `tar.gz`. Options are `tar.gz`, `tar.bz2`, `tbz`, `tbz2`, `tb2`, `bz2`, `tar`, `zip`
## Compare branches, tags or commits ## Compare branches, tags or commits
......
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
access_requesters = AccessRequestsFinder.new(source).execute!(current_user) access_requesters = AccessRequestsFinder.new(source).execute!(current_user)
access_requesters = paginate(access_requesters.includes(:user)) access_requesters = paginate(access_requesters.includes(:user))
present access_requesters.map(&:user), with: Entities::AccessRequester, source: source present access_requesters, with: Entities::AccessRequester
end end
desc "Requests access for the authenticated user to a #{source_type}." do desc "Requests access for the authenticated user to a #{source_type}." do
...@@ -36,7 +36,7 @@ module API ...@@ -36,7 +36,7 @@ module API
access_requester = source.request_access(current_user) access_requester = source.request_access(current_user)
if access_requester.persisted? if access_requester.persisted?
present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester present access_requester, with: Entities::AccessRequester
else else
render_validation_error!(access_requester) render_validation_error!(access_requester)
end end
...@@ -56,7 +56,7 @@ module API ...@@ -56,7 +56,7 @@ module API
member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute
status :created status :created
present member.user, with: Entities::Member, member: member present member, with: Entities::Member
end end
desc 'Denies an access request for the given user.' do desc 'Denies an access request for the given user.' do
......
...@@ -219,22 +219,15 @@ module API ...@@ -219,22 +219,15 @@ module API
expose :build_artifacts_size, as: :job_artifacts_size expose :build_artifacts_size, as: :job_artifacts_size
end end
class Member < UserBasic class Member < Grape::Entity
expose :access_level do |user, options| expose :user, merge: true, using: UserBasic
member = options[:member] || options[:source].members.find_by(user_id: user.id) expose :access_level
member.access_level expose :expires_at
end
expose :expires_at do |user, options|
member = options[:member] || options[:source].members.find_by(user_id: user.id)
member.expires_at
end
end end
class AccessRequester < UserBasic class AccessRequester < Grape::Entity
expose :requested_at do |user, options| expose :user, merge: true, using: UserBasic
access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id) expose :requested_at
access_requester.requested_at
end
end end
class LdapGroupLink < Grape::Entity class LdapGroupLink < Grape::Entity
......
...@@ -21,10 +21,11 @@ module API ...@@ -21,10 +21,11 @@ module API
get ":id/members" do get ":id/members" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
users = source.users members = source.members.where.not(user_id: nil).includes(:user)
users = users.merge(User.search(params[:query])) if params[:query].present? members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
members = paginate(members)
present paginate(users), with: Entities::Member, source: source present members, with: Entities::Member
end end
desc 'Gets a member of a group or project.' do desc 'Gets a member of a group or project.' do
...@@ -39,7 +40,7 @@ module API ...@@ -39,7 +40,7 @@ module API
members = source.members members = source.members
member = members.find_by!(user_id: params[:user_id]) member = members.find_by!(user_id: params[:user_id])
present member.user, with: Entities::Member, member: member present member, with: Entities::Member
end end
desc 'Adds a member to a group or project.' do desc 'Adds a member to a group or project.' do
...@@ -62,7 +63,7 @@ module API ...@@ -62,7 +63,7 @@ module API
if !member if !member
not_allowed! # This currently can only be reached in EE not_allowed! # This currently can only be reached in EE
elsif member.persisted? && member.valid? elsif member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member present member, with: Entities::Member
else else
render_validation_error!(member) render_validation_error!(member)
end end
...@@ -83,7 +84,7 @@ module API ...@@ -83,7 +84,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id)) member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false)) if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member present member, with: Entities::Member
else else
render_validation_error!(member) render_validation_error!(member)
end end
......
...@@ -22,10 +22,11 @@ module API ...@@ -22,10 +22,11 @@ module API
get ":id/members" do get ":id/members" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
users = source.users members = source.members.where.not(user_id: nil).includes(:user)
users = users.merge(User.search(params[:query])) if params[:query].present? members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
members = paginate(members)
present paginate(users), with: ::API::Entities::Member, source: source present members, with: ::API::Entities::Member
end end
desc 'Gets a member of a group or project.' do desc 'Gets a member of a group or project.' do
...@@ -40,7 +41,7 @@ module API ...@@ -40,7 +41,7 @@ module API
members = source.members members = source.members
member = members.find_by!(user_id: params[:user_id]) member = members.find_by!(user_id: params[:user_id])
present member.user, with: ::API::Entities::Member, member: member present member, with: ::API::Entities::Member
end end
desc 'Adds a member to a group or project.' do desc 'Adds a member to a group or project.' do
...@@ -69,7 +70,7 @@ module API ...@@ -69,7 +70,7 @@ module API
end end
if member.persisted? && member.valid? if member.persisted? && member.valid?
present member.user, with: ::API::Entities::Member, member: member present member, with: ::API::Entities::Member
else else
# This is to ensure back-compatibility but 400 behavior should be used # This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0! # for all validation errors in 9.0!
...@@ -93,7 +94,7 @@ module API ...@@ -93,7 +94,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id)) member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false)) if member.update_attributes(declared_params(include_missing: false))
present member.user, with: ::API::Entities::Member, member: member present member, with: ::API::Entities::Member
else else
# This is to ensure back-compatibility but 400 behavior should be used # This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0! # for all validation errors in 9.0!
...@@ -125,7 +126,7 @@ module API ...@@ -125,7 +126,7 @@ module API
else else
::Members::DestroyService.new(source, current_user, declared_params).execute ::Members::DestroyService.new(source, current_user, declared_params).execute
present member.user, with: ::API::Entities::Member, member: member present member, with: ::API::Entities::Member
end end
end end
end end
......
...@@ -888,16 +888,12 @@ module Gitlab ...@@ -888,16 +888,12 @@ module Gitlab
end end
def delete_refs(*ref_names) def delete_refs(*ref_names)
instructions = ref_names.map do |ref| gitaly_migrate(:delete_refs) do |is_enabled|
"delete #{ref}\x00\x00" if is_enabled
end gitaly_delete_refs(*ref_names)
else
message, status = run_git(%w[update-ref --stdin -z]) do |stdin| git_delete_refs(*ref_names)
stdin.write(instructions.join) end
end
unless status.zero?
raise GitError.new("Could not delete refs #{ref_names}: #{message}")
end end
end end
...@@ -1390,6 +1386,16 @@ module Gitlab ...@@ -1390,6 +1386,16 @@ module Gitlab
run_git(args).first.scrub.split(/^--$/) run_git(args).first.scrub.split(/^--$/)
end end
def can_be_merged?(source_sha, target_branch)
gitaly_migrate(:can_be_merged) do |is_enabled|
if is_enabled
gitaly_can_be_merged?(source_sha, find_branch(target_branch, true).target)
else
rugged_can_be_merged?(source_sha, target_branch)
end
end
end
def search_files_by_name(query, ref) def search_files_by_name(query, ref)
safe_query = Regexp.escape(query.sub(/^\/*/, "")) safe_query = Regexp.escape(query.sub(/^\/*/, ""))
...@@ -2204,6 +2210,24 @@ module Gitlab ...@@ -2204,6 +2210,24 @@ module Gitlab
remote_update(remote_name, url: url) remote_update(remote_name, url: url)
end end
def git_delete_refs(*ref_names)
instructions = ref_names.map do |ref|
"delete #{ref}\x00\x00"
end
message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
stdin.write(instructions.join)
end
unless status.zero?
raise GitError.new("Could not delete refs #{ref_names}: #{message}")
end
end
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names)
end
def rugged_remove_remote(remote_name) def rugged_remove_remote(remote_name)
# When a remote is deleted all its remote refs are deleted too, but in # When a remote is deleted all its remote refs are deleted too, but in
# the case of mirrors we map its refs (that would usualy go under # the case of mirrors we map its refs (that would usualy go under
...@@ -2266,6 +2290,14 @@ module Gitlab ...@@ -2266,6 +2290,14 @@ module Gitlab
run_git(['fetch', remote_name], env: env).last.zero? run_git(['fetch', remote_name], env: env).last.zero?
end end
def gitaly_can_be_merged?(their_commit, our_commit)
!gitaly_conflicts_client(our_commit, their_commit).conflicts?
end
def rugged_can_be_merged?(their_commit, our_commit)
!rugged.merge_commits(our_commit, their_commit).conflicts?
end
def gitlab_projects_error def gitlab_projects_error
raise CommandError, @gitlab_projects.output raise CommandError, @gitlab_projects.output
end end
......
...@@ -83,6 +83,8 @@ module Gitlab ...@@ -83,6 +83,8 @@ module Gitlab
commit_id: sha commit_id: sha
) )
end end
rescue Rugged::ReferenceError
[]
end end
end end
......
...@@ -103,7 +103,13 @@ module Gitlab ...@@ -103,7 +103,13 @@ module Gitlab
request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true)) request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true))
branch_update = response_enum.next.branch_update second_response = response_enum.next
if second_response.pre_receive_error.present?
raise Gitlab::Git::HooksService::PreReceiveError, second_response.pre_receive_error
end
branch_update = second_response.branch_update
return if branch_update.nil? return if branch_update.nil?
raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present? raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present?
......
...@@ -133,13 +133,16 @@ module Gitlab ...@@ -133,13 +133,16 @@ module Gitlab
GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request) GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request)
end end
def delete_refs(except_with_prefixes:) def delete_refs(refs: [], except_with_prefixes: [])
request = Gitaly::DeleteRefsRequest.new( request = Gitaly::DeleteRefsRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
except_with_prefix: except_with_prefixes refs: refs.map { |r| encode_binary(r) },
except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) }
) )
GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request) response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end end
private private
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-31 09:31+0100\n" "POT-Creation-Date: 2018-01-31 17:04+0100\n"
"PO-Revision-Date: 2018-01-31 09:31+0100\n" "PO-Revision-Date: 2018-01-31 17:04+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
......
...@@ -34,6 +34,9 @@ You can use GitLab QA to exercise tests on any live instance! For example, the ...@@ -34,6 +34,9 @@ You can use GitLab QA to exercise tests on any live instance! For example, the
following call would login to a local [GDK] instance and run all specs in following call would login to a local [GDK] instance and run all specs in
`qa/specs/features`: `qa/specs/features`:
First, `cd` into the `$gdk/gitlab/qa` directory.
The `bin/qa` script expects you to be in the `qa` folder of the app.
``` ```
bin/qa Test::Instance http://localhost:3000 bin/qa Test::Instance http://localhost:3000
``` ```
......
...@@ -155,6 +155,13 @@ module QA ...@@ -155,6 +155,13 @@ module QA
autoload :Main, 'qa/page/mattermost/main' autoload :Main, 'qa/page/mattermost/main'
autoload :Login, 'qa/page/mattermost/login' autoload :Login, 'qa/page/mattermost/login'
end end
##
# Classes describing components that are used by several pages.
#
module Component
autoload :Dropzone, 'qa/page/component/dropzone'
end
end end
## ##
......
...@@ -97,21 +97,6 @@ module QA ...@@ -97,21 +97,6 @@ module QA
views.map(&:errors).flatten views.map(&:errors).flatten
end end
# Not tested and not expected to work with multiple dropzones
# instantiated on one page because there is no distinguishing
# attribute per dropzone file field.
def attach_file_to_dropzone(attachment, dropzone_form_container)
filename = File.basename(attachment)
field_style = { visibility: 'visible', height: '', width: '' }
attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style)
# Wait for link to be appended to dropzone text
wait(reload: false) do
find("#{dropzone_form_container} textarea").value.match(filename)
end
end
class DSL class DSL
attr_reader :views attr_reader :views
......
module QA
module Page
module Component
class Dropzone
attr_reader :page, :container
def initialize(page, container)
@page = page
@container = container
end
# Not tested and not expected to work with multiple dropzones
# instantiated on one page because there is no distinguishing
# attribute per dropzone file field.
def attach_file(attachment)
filename = File.basename(attachment)
field_style = { visibility: 'visible', height: '', width: '' }
page.attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style)
# Wait for link to be appended to dropzone text
page.wait(reload: false) do
page.find("#{container} textarea").value.match(filename)
end
end
end
end
end
end
...@@ -23,10 +23,13 @@ module QA ...@@ -23,10 +23,13 @@ module QA
# Adds a comment to an issue # Adds a comment to an issue
# attachment option should be an absolute path # attachment option should be an absolute path
def comment(text, attachment:) def comment(text, attachment: nil)
fill_in(with: text, name: 'note[note]') fill_in(with: text, name: 'note[note]')
attach_file_to_dropzone(attachment, '.new-note') if attachment unless attachment.nil?
QA::Page::Component::Dropzone.new(page, '.new-note')
.attach_file(attachment)
end
click_on 'Comment' click_on 'Comment'
end end
......
...@@ -85,6 +85,30 @@ describe GroupsController do ...@@ -85,6 +85,30 @@ describe GroupsController do
end end
end end
describe 'GET #activity' do
render_views
before do
sign_in(user)
project
end
context 'as json' do
it 'includes all projects in event feed' do
3.times do
project = create(:project, group: group)
create(:event, project: project)
end
get :activity, id: group.to_param, format: :json
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq(3)
expect(assigns(:projects).limit_value).to be_nil
end
end
end
describe 'POST #create' do describe 'POST #create' do
it 'allows creating a group' do it 'allows creating a group' do
sign_in(user) sign_in(user)
......
...@@ -58,8 +58,7 @@ describe('Job', () => { ...@@ -58,8 +58,7 @@ describe('Job', () => {
it('updates the build trace on an interval', function () { it('updates the build trace on an interval', function () {
const deferred1 = $.Deferred(); const deferred1 = $.Deferred();
const deferred2 = $.Deferred(); const deferred2 = $.Deferred();
const deferred3 = $.Deferred(); spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
spyOn(urlUtils, 'visitUrl'); spyOn(urlUtils, 'visitUrl');
deferred1.resolve({ deferred1.resolve({
...@@ -70,9 +69,7 @@ describe('Job', () => { ...@@ -70,9 +69,7 @@ describe('Job', () => {
complete: false, complete: false,
}); });
deferred2.resolve(); deferred2.resolve({
deferred3.resolve({
html: '<span>More</span>', html: '<span>More</span>',
status: 'running', status: 'running',
state: 'finalstate', state: 'finalstate',
...@@ -94,9 +91,8 @@ describe('Job', () => { ...@@ -94,9 +91,8 @@ describe('Job', () => {
it('replaces the entire build trace', () => { it('replaces the entire build trace', () => {
const deferred1 = $.Deferred(); const deferred1 = $.Deferred();
const deferred2 = $.Deferred(); const deferred2 = $.Deferred();
const deferred3 = $.Deferred();
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise()); spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl'); spyOn(urlUtils, 'visitUrl');
...@@ -107,9 +103,7 @@ describe('Job', () => { ...@@ -107,9 +103,7 @@ describe('Job', () => {
complete: false, complete: false,
}); });
deferred2.resolve(); deferred2.resolve({
deferred3.resolve({
html: '<span>Different</span>', html: '<span>Different</span>',
status: 'running', status: 'running',
append: false, append: false,
...@@ -170,9 +164,8 @@ describe('Job', () => { ...@@ -170,9 +164,8 @@ describe('Job', () => {
it('shows incremented size', () => { it('shows incremented size', () => {
const deferred1 = $.Deferred(); const deferred1 = $.Deferred();
const deferred2 = $.Deferred(); const deferred2 = $.Deferred();
const deferred3 = $.Deferred();
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise()); spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl'); spyOn(urlUtils, 'visitUrl');
...@@ -184,8 +177,6 @@ describe('Job', () => { ...@@ -184,8 +177,6 @@ describe('Job', () => {
total: 100, total: 100,
}); });
deferred2.resolve();
this.job = new Job(); this.job = new Job();
expect( expect(
...@@ -194,7 +185,7 @@ describe('Job', () => { ...@@ -194,7 +185,7 @@ describe('Job', () => {
jasmine.clock().tick(4001); jasmine.clock().tick(4001);
deferred3.resolve({ deferred2.resolve({
html: '<span>Update</span>', html: '<span>Update</span>',
status: 'success', status: 'success',
append: true, append: true,
......
/* eslint-disable promise/catch-or-return */ /* eslint-disable promise/catch-or-return */
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
describe('common_utils', () => { describe('common_utils', () => {
describe('parseUrl', () => { describe('parseUrl', () => {
...@@ -413,37 +415,48 @@ describe('common_utils', () => { ...@@ -413,37 +415,48 @@ describe('common_utils', () => {
describe('setCiStatusFavicon', () => { describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`; const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
let mock;
beforeEach(() => { beforeEach(() => {
const favicon = document.createElement('link'); const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon'); favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon); document.body.appendChild(favicon);
mock = new MockAdapter(axios);
}); });
afterEach(() => { afterEach(() => {
mock.restore();
document.body.removeChild(document.getElementById('favicon')); document.body.removeChild(document.getElementById('favicon'));
}); });
it('should reset favicon in case of error', () => { it('should reset favicon in case of error', (done) => {
const favicon = document.getElementById('favicon'); mock.onGet(BUILD_URL).networkError();
spyOn($, 'ajax').and.callFake(function (options) {
options.error();
expect(favicon.getAttribute('href')).toEqual('null');
});
commonUtils.setCiStatusFavicon(BUILD_URL); commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => {
const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual('null');
done();
})
// Error is already caught in catch() block of setCiStatusFavicon,
// It won't throw another error for us to catch
.catch(done.fail);
}); });
it('should set page favicon to CI status favicon based on provided status', () => { it('should set page favicon to CI status favicon based on provided status', (done) => {
const FAVICON_PATH = '//icon_status_success'; const FAVICON_PATH = '//icon_status_success';
const favicon = document.getElementById('favicon');
spyOn($, 'ajax').and.callFake(function (options) { mock.onGet(BUILD_URL).reply(200, {
options.success({ favicon: FAVICON_PATH }); favicon: FAVICON_PATH,
expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
}); });
commonUtils.setCiStatusFavicon(BUILD_URL); commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => {
const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
done();
})
.catch(done.fail);
}); });
}); });
......
...@@ -562,35 +562,39 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -562,35 +562,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
describe '#delete_refs' do describe '#delete_refs' do
before(:all) do shared_examples 'deleting refs' do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
end
it 'deletes the ref' do after do
@repo.delete_refs('refs/heads/feature') ensure_seeds
end
expect(@repo.rugged.references['refs/heads/feature']).to be_nil it 'deletes the ref' do
end repo.delete_refs('refs/heads/feature')
it 'deletes all refs' do expect(repo.rugged.references['refs/heads/feature']).to be_nil
refs = %w[refs/heads/wip refs/tags/v1.1.0] end
@repo.delete_refs(*refs)
refs.each do |ref| it 'deletes all refs' do
expect(@repo.rugged.references[ref]).to be_nil refs = %w[refs/heads/wip refs/tags/v1.1.0]
repo.delete_refs(*refs)
refs.each do |ref|
expect(repo.rugged.references[ref]).to be_nil
end
end end
end
it 'raises an error if it failed' do it 'raises an error if it failed' do
expect(@repo).to receive(:popen).and_return(['Error', 1]) expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
end
end
expect do context 'when Gitaly delete_refs feature is enabled' do
@repo.delete_refs('refs/heads/fix') it_behaves_like 'deleting refs'
end.to raise_error(Gitlab::Git::Repository::GitError)
end end
after(:all) do context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do
ensure_seeds it_behaves_like 'deleting refs'
end end
end end
......
...@@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do ...@@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do
end end
describe '#where' do describe '#where' do
context 'with gitaly disabled' do shared_examples '#where' do
before do it 'returns an empty array when called with an invalid ref' do
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
end
it 'calls #tree_entries_from_rugged' do
expect(described_class).to receive(:tree_entries_from_rugged)
described_class.where(repository, SeedRepo::Commit::ID, '/')
end end
end end
it 'gets the tree entries from GitalyClient' do context 'with gitaly' do
expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries) it_behaves_like '#where'
end
described_class.where(repository, SeedRepo::Commit::ID, '/') context 'without gitaly', :skip_gitaly_mock do
it_behaves_like '#where'
end end
end end
end end
...@@ -112,7 +112,7 @@ describe Gitlab::GitalyClient::RefService do ...@@ -112,7 +112,7 @@ describe Gitlab::GitalyClient::RefService do
expect_any_instance_of(Gitaly::RefService::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:delete_refs) .to receive(:delete_refs)
.with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash)) .with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
.and_return(double('delete_refs_response')) .and_return(double('delete_refs_response', git_error: ""))
client.delete_refs(except_with_prefixes: prefixes) client.delete_refs(except_with_prefixes: prefixes)
end end
......
...@@ -804,8 +804,7 @@ describe Repository do ...@@ -804,8 +804,7 @@ describe Repository do
user, 'LICENSE', 'Copyright!', user, 'LICENSE', 'Copyright!',
message: 'Add LICENSE', branch_name: 'master') message: 'Add LICENSE', branch_name: 'master')
allow(repository).to receive(:file_on_head) allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
.and_raise(Rugged::ReferenceError)
expect(repository.license_blob).to be_nil expect(repository.license_blob).to be_nil
end end
...@@ -917,7 +916,7 @@ describe Repository do ...@@ -917,7 +916,7 @@ describe Repository do
end end
it 'returns nil for empty repository' do it 'returns nil for empty repository' do
allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError) allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.gitlab_ci_yml).to be_nil expect(repository.gitlab_ci_yml).to be_nil
end end
end end
...@@ -2011,8 +2010,7 @@ describe Repository do ...@@ -2011,8 +2010,7 @@ describe Repository do
describe '#avatar' do describe '#avatar' do
it 'returns nil if repo does not exist' do it 'returns nil if repo does not exist' do
expect(repository).to receive(:file_on_head) allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
.and_raise(Rugged::ReferenceError)
expect(repository.avatar).to eq(nil) expect(repository.avatar).to eq(nil)
end end
......
...@@ -44,6 +44,21 @@ describe API::Members do ...@@ -44,6 +44,21 @@ describe API::Members do
end end
end end
it 'avoids N+1 queries' do
# Establish baseline
get api("/#{source_type.pluralize}/#{source.id}/members", master)
control = ActiveRecord::QueryRecorder.new do
get api("/#{source_type.pluralize}/#{source.id}/members", master)
end
project.add_developer(create(:user))
expect do
get api("/#{source_type.pluralize}/#{source.id}/members", master)
end.not_to exceed_query_limit(control)
end
it 'does not return invitees' do it 'does not return invitees' do
create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
......
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