Commit 8d6aee1b authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-01-31

# Conflicts:
#	app/assets/javascripts/api.js
#	app/views/admin/dashboard/index.html.haml
#	locale/gitlab.pot
#	spec/javascripts/api_spec.js

[ci skip]
parents 6087548f 078dac42
--- ---
engines: engines:
brakeman:
enabled: true
bundler-audit: bundler-audit:
enabled: true enabled: true
duplication: duplication:
......
...@@ -26,7 +26,15 @@ const Api = { ...@@ -26,7 +26,15 @@ 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)
<<<<<<< HEAD
.then(({ data }) => callback(data)); .then(({ data }) => callback(data));
=======
.then(({ data }) => {
callback(data);
return data;
});
>>>>>>> upstream/master
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
...@@ -186,6 +194,7 @@ const Api = { ...@@ -186,6 +194,7 @@ const Api = {
search: query, search: query,
per_page: 20, per_page: 20,
}, options), }, options),
<<<<<<< HEAD
}); });
}, },
...@@ -215,6 +224,8 @@ const Api = { ...@@ -215,6 +224,8 @@ const Api = {
callback(data); callback(data);
return data; return data;
=======
>>>>>>> upstream/master
}); });
}, },
......
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,6 +158,7 @@ ...@@ -158,6 +158,7 @@
GitLab Pages GitLab Pages
%span.pull-right %span.pull-right
= Gitlab::Pages::VERSION = Gitlab::Pages::VERSION
<<<<<<< HEAD
- if Gitlab::Geo.enabled? - if Gitlab::Geo.enabled?
%p %p
...@@ -168,6 +169,8 @@ ...@@ -168,6 +169,8 @@
- else - else
Undefined Undefined
=======
>>>>>>> upstream/master
%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,13 @@ msgid "" ...@@ -8,8 +8,13 @@ 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"
<<<<<<< HEAD
"POT-Creation-Date: 2018-01-31 09:31+0100\n" "POT-Creation-Date: 2018-01-31 09:31+0100\n"
"PO-Revision-Date: 2018-01-31 09:31+0100\n" "PO-Revision-Date: 2018-01-31 09:31+0100\n"
=======
"POT-Creation-Date: 2018-01-30 14:59+0100\n"
"PO-Revision-Date: 2018-01-30 14:59+0100\n"
>>>>>>> upstream/master
"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"
...@@ -157,9 +162,12 @@ msgstr "" ...@@ -157,9 +162,12 @@ msgstr ""
msgid "All" msgid "All"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "All changes are committed" msgid "All changes are committed"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "An error occurred previewing the blob" msgid "An error occurred previewing the blob"
msgstr "" msgstr ""
...@@ -259,6 +267,7 @@ msgstr "" ...@@ -259,6 +267,7 @@ msgstr ""
msgid "Avatar will be removed. Are you sure?" msgid "Avatar will be removed. Are you sure?"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Billing" msgid "Billing"
msgstr "" msgstr ""
...@@ -313,6 +322,8 @@ msgstr "" ...@@ -313,6 +322,8 @@ msgstr ""
msgid "BillingPlans|per user" msgid "BillingPlans|per user"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Branch" msgid "Branch"
msgid_plural "Branches" msgid_plural "Branches"
msgstr[0] "" msgstr[0] ""
...@@ -507,9 +518,12 @@ msgstr "" ...@@ -507,9 +518,12 @@ msgstr ""
msgid "Choose file..." msgid "Choose file..."
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "CiStatusLabel|canceled" msgid "CiStatusLabel|canceled"
msgstr "" msgstr ""
...@@ -579,6 +593,7 @@ msgstr "" ...@@ -579,6 +593,7 @@ msgstr ""
msgid "Closed" msgid "Closed"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Cluster" msgid "Cluster"
msgstr "" msgstr ""
...@@ -588,6 +603,11 @@ msgstr "" ...@@ -588,6 +603,11 @@ msgstr ""
msgid "ClusterIntegration|API URL" msgid "ClusterIntegration|API URL"
msgstr "" msgstr ""
=======
msgid "ClusterIntegration|API URL"
msgstr ""
>>>>>>> upstream/master
msgid "ClusterIntegration|Add an existing cluster" msgid "ClusterIntegration|Add an existing cluster"
msgstr "" msgstr ""
...@@ -738,9 +758,12 @@ msgstr "" ...@@ -738,9 +758,12 @@ msgstr ""
msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "ClusterIntegration|Multiple clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgid "ClusterIntegration|Multiple clusters are available in GitLab Enterprise Edition Premium and Ultimate"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "ClusterIntegration|Note:" msgid "ClusterIntegration|Note:"
msgstr "" msgstr ""
...@@ -1231,6 +1254,7 @@ msgstr "" ...@@ -1231,6 +1254,7 @@ msgstr ""
msgid "Environments|You don't have any environments right now." msgid "Environments|You don't have any environments right now."
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Epic will be removed! Are you sure?" msgid "Epic will be removed! Are you sure?"
msgstr "" msgstr ""
...@@ -1246,6 +1270,8 @@ msgstr "" ...@@ -1246,6 +1270,8 @@ msgstr ""
msgid "Error fetching contributors data." msgid "Error fetching contributors data."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Error fetching refs" msgid "Error fetching refs"
msgstr "" msgstr ""
...@@ -1345,12 +1371,30 @@ msgid "GPG Keys" ...@@ -1345,12 +1371,30 @@ msgid "GPG Keys"
msgstr "" msgstr ""
msgid "Generate a default set of labels" msgid "Generate a default set of labels"
<<<<<<< HEAD
msgstr "" msgstr ""
msgid "Geo Nodes" msgid "Geo Nodes"
msgstr "" msgstr ""
msgid "GeoNodeSyncStatus|Node is failing or broken." msgid "GeoNodeSyncStatus|Node is failing or broken."
=======
msgstr ""
msgid "Git storage health information has been reset"
msgstr ""
msgid "Git version"
msgstr ""
msgid "GitLab Runner section"
msgstr ""
msgid "Gitaly Servers"
msgstr ""
msgid "Go to your fork"
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
...@@ -1395,10 +1439,14 @@ msgstr "" ...@@ -1395,10 +1439,14 @@ msgstr ""
msgid "GeoNodes|Local LFS objects:" msgid "GeoNodes|Local LFS objects:"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "GeoNodes|Local job artifacts:" msgid "GeoNodes|Local job artifacts:"
msgstr "" msgstr ""
msgid "GeoNodes|New node" msgid "GeoNodes|New node"
=======
msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "GeoNodes|Out of sync" msgid "GeoNodes|Out of sync"
...@@ -1446,7 +1494,16 @@ msgstr "" ...@@ -1446,7 +1494,16 @@ msgstr ""
msgid "Geo|Repository sync capacity" msgid "Geo|Repository sync capacity"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Geo|Select groups to replicate." msgid "Geo|Select groups to replicate."
=======
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
msgstr[1] ""
msgid "History"
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "Git storage health information has been reset" msgid "Git storage health information has been reset"
...@@ -1458,7 +1515,14 @@ msgstr "" ...@@ -1458,7 +1515,14 @@ msgstr ""
msgid "GitLab Runner section" msgid "GitLab Runner section"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Gitaly Servers" msgid "Gitaly Servers"
=======
msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr ""
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "Go to your fork" msgid "Go to your fork"
...@@ -1479,6 +1543,7 @@ msgstr "" ...@@ -1479,6 +1543,7 @@ msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}." msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
msgstr "" msgstr ""
...@@ -1618,6 +1683,8 @@ msgstr "" ...@@ -1618,6 +1683,8 @@ msgstr ""
msgid "Issues" msgid "Issues"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr "" msgstr ""
...@@ -1698,9 +1765,12 @@ msgstr "" ...@@ -1698,9 +1765,12 @@ msgstr ""
msgid "Leave project" msgid "Leave project"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "License" msgid "License"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Loading the GitLab IDE..." msgid "Loading the GitLab IDE..."
msgstr "" msgstr ""
...@@ -1835,9 +1905,12 @@ msgstr "" ...@@ -1835,9 +1905,12 @@ msgstr ""
msgid "New tag" msgid "New tag"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "No changes" msgid "No changes"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "No connection could be made to a Gitaly Server, please check your logs!" msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr "" msgstr ""
...@@ -1934,9 +2007,12 @@ msgstr "" ...@@ -1934,9 +2007,12 @@ msgstr ""
msgid "Number of access attempts" msgid "Number of access attempts"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "OK" msgid "OK"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Oct" msgid "Oct"
msgstr "" msgstr ""
...@@ -2213,6 +2289,7 @@ msgstr "" ...@@ -2213,6 +2289,7 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph" msgid "ProjectNetworkGraph|Graph"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "ProjectSettings|Contact an admin to change this setting." msgid "ProjectSettings|Contact an admin to change this setting."
msgstr "" msgstr ""
...@@ -2231,6 +2308,8 @@ msgstr "" ...@@ -2231,6 +2308,8 @@ msgstr ""
msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Projects" msgid "Projects"
msgstr "" msgstr ""
...@@ -2318,9 +2397,12 @@ msgstr "" ...@@ -2318,9 +2397,12 @@ msgstr ""
msgid "Register / Sign In" msgid "Register / Sign In"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Registry" msgid "Registry"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Related Commits" msgid "Related Commits"
msgstr "" msgstr ""
...@@ -2342,9 +2424,12 @@ msgstr "" ...@@ -2342,9 +2424,12 @@ msgstr ""
msgid "Remind later" msgid "Remind later"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Remove avatar" msgid "Remove avatar"
msgstr "" msgstr ""
...@@ -2404,9 +2489,12 @@ msgstr "" ...@@ -2404,9 +2489,12 @@ msgstr ""
msgid "Search branches and tags" msgid "Search branches and tags"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Seconds before reseting failure information" msgid "Seconds before reseting failure information"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Seconds to wait for a storage access attempt" msgid "Seconds to wait for a storage access attempt"
msgstr "" msgstr ""
...@@ -2490,7 +2578,7 @@ msgstr "" ...@@ -2490,7 +2578,7 @@ msgstr ""
msgid "Something went wrong on our end." msgid "Something went wrong on our end."
msgstr "" msgstr ""
msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgid "Something went wrong when toggling the button"
msgstr "" msgstr ""
msgid "Something went wrong when toggling the button" msgid "Something went wrong when toggling the button"
...@@ -2729,12 +2817,15 @@ msgstr "" ...@@ -2729,12 +2817,15 @@ msgstr ""
msgid "Team" msgid "Team"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Thanks! Don't show me this again" msgid "Thanks! Don't show me this again"
msgstr "" msgstr ""
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr "" msgstr ""
...@@ -2813,6 +2904,7 @@ msgstr "" ...@@ -2813,6 +2904,7 @@ msgstr ""
msgid "There are problems accessing Git storage: " msgid "There are problems accessing Git storage: "
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "There was an error when reseting email token." msgid "There was an error when reseting email token."
msgstr "" msgstr ""
...@@ -2825,6 +2917,8 @@ msgstr "" ...@@ -2825,6 +2917,8 @@ msgstr ""
msgid "This board\\'s scope is reduced" msgid "This board\\'s scope is reduced"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "This directory" msgid "This directory"
msgstr "" msgstr ""
...@@ -2870,9 +2964,12 @@ msgstr "" ...@@ -2870,9 +2964,12 @@ msgstr ""
msgid "This repository" msgid "This repository"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "Time before an issue gets scheduled" msgid "Time before an issue gets scheduled"
msgstr "" msgstr ""
...@@ -3021,9 +3118,12 @@ msgstr[1] "" ...@@ -3021,9 +3118,12 @@ msgstr[1] ""
msgid "Time|s" msgid "Time|s"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Title" msgid "Title"
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "ToggleButton|Toggle Status: OFF" msgid "ToggleButton|Toggle Status: OFF"
msgstr "" msgstr ""
...@@ -3039,6 +3139,7 @@ msgstr "" ...@@ -3039,6 +3139,7 @@ msgstr ""
msgid "Total test time for all commits/merges" msgid "Total test time for all commits/merges"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Track activity with Contribution Analytics." msgid "Track activity with Contribution Analytics."
msgstr "" msgstr ""
...@@ -3057,6 +3158,14 @@ msgstr "" ...@@ -3057,6 +3158,14 @@ msgstr ""
msgid "Unknown" msgid "Unknown"
msgstr "" msgstr ""
=======
msgid "Trigger this manual action"
msgstr ""
msgid "Unable to reset project cache."
msgstr ""
>>>>>>> upstream/master
msgid "Unlock" msgid "Unlock"
msgstr "" msgstr ""
...@@ -3067,6 +3176,7 @@ msgid "Unstar" ...@@ -3067,6 +3176,7 @@ msgid "Unstar"
msgstr "" msgstr ""
msgid "Up to date" msgid "Up to date"
<<<<<<< HEAD
msgstr "" msgstr ""
msgid "Upgrade your plan to activate Advanced Global Search." msgid "Upgrade your plan to activate Advanced Global Search."
...@@ -3082,6 +3192,8 @@ msgid "Upgrade your plan to activate Issue weight." ...@@ -3082,6 +3192,8 @@ msgid "Upgrade your plan to activate Issue weight."
msgstr "" msgstr ""
msgid "Upgrade your plan to improve Issue boards." msgid "Upgrade your plan to improve Issue boards."
=======
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "Upload New File" msgid "Upload New File"
...@@ -3136,12 +3248,15 @@ msgid "We don't have enough data to show this stage." ...@@ -3136,12 +3248,15 @@ msgid "We don't have enough data to show this stage."
msgstr "" msgstr ""
msgid "We want to be sure it is you, please confirm you are not a robot." msgid "We want to be sure it is you, please confirm you are not a robot."
<<<<<<< HEAD
msgstr "" msgstr ""
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "" msgstr ""
msgid "Weight" msgid "Weight"
=======
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "Wiki" msgid "Wiki"
...@@ -3267,15 +3382,21 @@ msgstr "" ...@@ -3267,15 +3382,21 @@ msgstr ""
msgid "You can also star a label to make it a priority label." msgid "You can also star a label to make it a priority label."
msgstr "" msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
msgid "You can only add files when you are on a branch" msgid "You can only add files when you are on a branch"
msgstr "" msgstr ""
msgid "You can only edit files when you are on a branch" msgid "You can only edit files when you are on a branch"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "You cannot write to this read-only GitLab instance." msgid "You cannot write to this read-only GitLab instance."
msgstr "" msgstr ""
......
...@@ -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)
......
...@@ -134,6 +134,7 @@ describe('Api', () => { ...@@ -134,6 +134,7 @@ describe('Api', () => {
Api.newLabel(namespace, project, labelData, (response) => { Api.newLabel(namespace, project, labelData, (response) => {
expect(response.name).toBe('test'); expect(response.name).toBe('test');
<<<<<<< HEAD
done(); done();
}); });
}); });
...@@ -155,6 +156,8 @@ describe('Api', () => { ...@@ -155,6 +156,8 @@ describe('Api', () => {
Api.newLabel(namespace, null, labelData, (response) => { Api.newLabel(namespace, null, labelData, (response) => {
expect(response.name).toBe('test'); expect(response.name).toBe('test');
=======
>>>>>>> upstream/master
done(); done();
}); });
}); });
...@@ -259,6 +262,7 @@ describe('Api', () => { ...@@ -259,6 +262,7 @@ describe('Api', () => {
.then(({ data }) => { .then(({ data }) => {
expect(data.length).toBe(1); expect(data.length).toBe(1);
expect(data[0].name).toBe('test'); expect(data[0].name).toBe('test');
<<<<<<< HEAD
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -279,6 +283,8 @@ describe('Api', () => { ...@@ -279,6 +283,8 @@ describe('Api', () => {
Api.ldap_groups(query, provider, callback) Api.ldap_groups(query, provider, callback)
.then((response) => { .then((response) => {
expect(callback).toHaveBeenCalledWith(response); expect(callback).toHaveBeenCalledWith(response);
=======
>>>>>>> upstream/master
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
...@@ -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