mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-08-16 14:53:17 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -5,7 +5,7 @@ import { formatNumber } from '~/locale';
|
||||
*
|
||||
* @param {Number} number to be converted
|
||||
*
|
||||
* @param {options.maxCharLength} Max output char length at the
|
||||
* @param {options.maxLength} Max output char length at the
|
||||
* expense of precision, if the output is longer than this,
|
||||
* the formatter switches to using exponential notation.
|
||||
*
|
||||
@ -16,16 +16,35 @@ import { formatNumber } from '~/locale';
|
||||
* `formatNumber` such as `valueFactor`, `unit` and `style`.
|
||||
*
|
||||
*/
|
||||
const formatNumberNormalized = (value, { maxCharLength, valueFactor = 1, ...options }) => {
|
||||
const formatNumberNormalized = (value, { maxLength, valueFactor = 1, ...options }) => {
|
||||
const formatted = formatNumber(value * valueFactor, options);
|
||||
|
||||
if (maxCharLength !== undefined && formatted.length > maxCharLength) {
|
||||
if (maxLength !== undefined && formatted.length > maxLength) {
|
||||
// 123456 becomes 1.23e+8
|
||||
return value.toExponential(2);
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function converts the old positional arguments into an options
|
||||
* object.
|
||||
*
|
||||
* This is done so we can support legacy fractionDigits and maxLength as positional
|
||||
* arguments, as well as the better options object.
|
||||
*
|
||||
* @param {Object|Number} options
|
||||
* @returns {Object} options given to the formatter
|
||||
*/
|
||||
const getFormatterArguments = (options) => {
|
||||
if (typeof options === 'object' && options !== null) {
|
||||
return options;
|
||||
}
|
||||
return {
|
||||
maxLength: options,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a number as a string scaling it up according to units.
|
||||
*
|
||||
@ -40,7 +59,9 @@ const scaledFormatter = (units, unitFactor = 1000) => {
|
||||
return new RangeError(`unitFactor cannot have the value 0.`);
|
||||
}
|
||||
|
||||
return (value, fractionDigits) => {
|
||||
return (value, fractionDigits, options) => {
|
||||
const { maxLength, unitSeparator = '' } = getFormatterArguments(options);
|
||||
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
@ -66,11 +87,13 @@ const scaledFormatter = (units, unitFactor = 1000) => {
|
||||
}
|
||||
|
||||
const unit = units[scale];
|
||||
const length = maxLength !== undefined ? maxLength - unit.length : undefined;
|
||||
|
||||
return `${formatNumberNormalized(num, {
|
||||
maxLength: length,
|
||||
maximumFractionDigits: fractionDigits,
|
||||
minimumFractionDigits: fractionDigits,
|
||||
})}${unit}`;
|
||||
})}${unitSeparator}${unit}`;
|
||||
};
|
||||
};
|
||||
|
||||
@ -78,14 +101,16 @@ const scaledFormatter = (units, unitFactor = 1000) => {
|
||||
* Returns a function that formats a number as a string.
|
||||
*/
|
||||
export const numberFormatter = (style = 'decimal', valueFactor = 1) => {
|
||||
return (value, fractionDigits, maxCharLength) => {
|
||||
return `${formatNumberNormalized(value, {
|
||||
maxCharLength,
|
||||
return (value, fractionDigits, options) => {
|
||||
const { maxLength } = getFormatterArguments(options);
|
||||
|
||||
return formatNumberNormalized(value, {
|
||||
maxLength,
|
||||
valueFactor,
|
||||
style,
|
||||
maximumFractionDigits: fractionDigits,
|
||||
minimumFractionDigits: fractionDigits,
|
||||
})}`;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -93,15 +118,16 @@ export const numberFormatter = (style = 'decimal', valueFactor = 1) => {
|
||||
* Returns a function that formats a number as a string with a suffix.
|
||||
*/
|
||||
export const suffixFormatter = (unit = '', valueFactor = 1) => {
|
||||
return (value, fractionDigits, maxCharLength) => {
|
||||
const length = maxCharLength !== undefined ? maxCharLength - unit.length : undefined;
|
||||
return (value, fractionDigits, options) => {
|
||||
const { maxLength, unitSeparator = '' } = getFormatterArguments(options);
|
||||
|
||||
const length = maxLength !== undefined ? maxLength - unit.length : undefined;
|
||||
return `${formatNumberNormalized(value, {
|
||||
maxCharLength: length,
|
||||
maxLength: length,
|
||||
valueFactor,
|
||||
maximumFractionDigits: fractionDigits,
|
||||
minimumFractionDigits: fractionDigits,
|
||||
})}${unit}`;
|
||||
})}${unitSeparator}${unit}`;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -126,9 +126,11 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format
|
||||
* @param {Number} fractionDigits - precision decimals
|
||||
* @param {Number} maxLength - Max length of formatted number
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const number = getFormatter(SUPPORTED_FORMATS.number);
|
||||
|
||||
@ -137,9 +139,11 @@ export const number = getFormatter(SUPPORTED_FORMATS.number);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is rendered as `100%`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Number} maxLength - Max length of formatted number
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const percent = getFormatter(SUPPORTED_FORMATS.percent);
|
||||
|
||||
@ -148,9 +152,11 @@ export const percent = getFormatter(SUPPORTED_FORMATS.percent);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `100` is rendered as `100%`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Number} maxLength - Max length of formatted number
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const percentHundred = getFormatter(SUPPORTED_FORMATS.percentHundred);
|
||||
|
||||
@ -159,9 +165,11 @@ export const percentHundred = getFormatter(SUPPORTED_FORMATS.percentHundred);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is rendered as `1s`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Number} maxLength - Max length of formatted number
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const seconds = getFormatter(SUPPORTED_FORMATS.seconds);
|
||||
|
||||
@ -170,9 +178,11 @@ export const seconds = getFormatter(SUPPORTED_FORMATS.seconds);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1ms`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Number} maxLength - Max length of formatted number
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const milliseconds = getFormatter(SUPPORTED_FORMATS.milliseconds);
|
||||
|
||||
@ -182,7 +192,11 @@ export const milliseconds = getFormatter(SUPPORTED_FORMATS.milliseconds);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1B`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const decimalBytes = getFormatter(SUPPORTED_FORMATS.decimalBytes);
|
||||
|
||||
@ -192,7 +206,11 @@ export const decimalBytes = getFormatter(SUPPORTED_FORMATS.decimalBytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1kB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const kilobytes = getFormatter(SUPPORTED_FORMATS.kilobytes);
|
||||
|
||||
@ -202,7 +220,11 @@ export const kilobytes = getFormatter(SUPPORTED_FORMATS.kilobytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1MB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const megabytes = getFormatter(SUPPORTED_FORMATS.megabytes);
|
||||
|
||||
@ -212,7 +234,11 @@ export const megabytes = getFormatter(SUPPORTED_FORMATS.megabytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1GB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const gigabytes = getFormatter(SUPPORTED_FORMATS.gigabytes);
|
||||
|
||||
@ -222,7 +248,11 @@ export const gigabytes = getFormatter(SUPPORTED_FORMATS.gigabytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1GB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const terabytes = getFormatter(SUPPORTED_FORMATS.terabytes);
|
||||
|
||||
@ -232,7 +262,11 @@ export const terabytes = getFormatter(SUPPORTED_FORMATS.terabytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1PB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const petabytes = getFormatter(SUPPORTED_FORMATS.petabytes);
|
||||
|
||||
@ -242,7 +276,11 @@ export const petabytes = getFormatter(SUPPORTED_FORMATS.petabytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1B`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const bytes = getFormatter(SUPPORTED_FORMATS.bytes);
|
||||
|
||||
@ -252,7 +290,11 @@ export const bytes = getFormatter(SUPPORTED_FORMATS.bytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1kB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const kibibytes = getFormatter(SUPPORTED_FORMATS.kibibytes);
|
||||
|
||||
@ -262,7 +304,11 @@ export const kibibytes = getFormatter(SUPPORTED_FORMATS.kibibytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1MB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const mebibytes = getFormatter(SUPPORTED_FORMATS.mebibytes);
|
||||
|
||||
@ -272,7 +318,11 @@ export const mebibytes = getFormatter(SUPPORTED_FORMATS.mebibytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1GB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const gibibytes = getFormatter(SUPPORTED_FORMATS.gibibytes);
|
||||
|
||||
@ -282,7 +332,11 @@ export const gibibytes = getFormatter(SUPPORTED_FORMATS.gibibytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1GB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const tebibytes = getFormatter(SUPPORTED_FORMATS.tebibytes);
|
||||
|
||||
@ -292,7 +346,11 @@ export const tebibytes = getFormatter(SUPPORTED_FORMATS.tebibytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Number to format, `1` is formatted as `1PB`
|
||||
* @param {Number} fractionDigits - number of precision decimals
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - number of precision decimals
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const pebibytes = getFormatter(SUPPORTED_FORMATS.pebibytes);
|
||||
|
||||
@ -301,6 +359,10 @@ export const pebibytes = getFormatter(SUPPORTED_FORMATS.pebibytes);
|
||||
*
|
||||
* @function
|
||||
* @param {Number} value - Value to format
|
||||
* @param {Number} fractionDigits - precision decimals - Defaults to 2
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {Number} options.fractionDigits - precision decimals, defaults to 2
|
||||
* @param {Number} options.maxLength - Max length of formatted number
|
||||
* if length is exceeded, exponential format is used.
|
||||
* @param {String} options.unitSeparator - Separator between value and unit
|
||||
*/
|
||||
export const engineering = getFormatter();
|
||||
|
@ -13,9 +13,7 @@ module Integrations
|
||||
validate :number_of_recipients_within_limit, if: :validate_recipients?
|
||||
|
||||
def self.valid_recipients(recipients)
|
||||
recipients.split.select do |recipient|
|
||||
recipient.include?('@')
|
||||
end.uniq(&:downcase)
|
||||
recipients.split.grep(Devise.email_regexp).uniq(&:downcase)
|
||||
end
|
||||
|
||||
def title
|
||||
|
@ -82,10 +82,6 @@ class ProjectMember < Member
|
||||
source
|
||||
end
|
||||
|
||||
def owner?
|
||||
project.owner == user
|
||||
end
|
||||
|
||||
def notifiable_options
|
||||
{ project: project }
|
||||
end
|
||||
@ -132,7 +128,10 @@ class ProjectMember < Member
|
||||
end
|
||||
|
||||
def post_create_hook
|
||||
unless owner?
|
||||
# The creator of a personal project gets added as a `ProjectMember`
|
||||
# with `OWNER` access during creation of a personal project,
|
||||
# but we do not want to trigger notifications to the same person who created the personal project.
|
||||
unless project.personal_namespace_holder?(user)
|
||||
event_service.join_project(self.project, self.user)
|
||||
run_after_commit_or_now { notification_service.new_project_member(self) }
|
||||
end
|
||||
|
@ -899,6 +899,18 @@ class Project < ApplicationRecord
|
||||
association(:namespace).loaded?
|
||||
end
|
||||
|
||||
def personal_namespace_holder?(user)
|
||||
return false unless personal?
|
||||
return false unless user
|
||||
|
||||
# We do not want to use a check like `project.team.owner?(user)`
|
||||
# here because that would depend upon the state of the `project_authorizations` cache,
|
||||
# and also perform the check across multiple `owners` of the project, but our intention
|
||||
# is to check if the user is the "holder" of the personal namespace, so need to make this
|
||||
# check against only a single user (ie, namespace.owner).
|
||||
namespace.owner == user
|
||||
end
|
||||
|
||||
def project_setting
|
||||
super.presence || build_project_setting
|
||||
end
|
||||
|
@ -10,12 +10,12 @@ module Members
|
||||
private
|
||||
|
||||
def can_update_member?
|
||||
super || current_user.can?(:update_project_member, member) || adding_a_new_owner?
|
||||
super || current_user.can?(:update_project_member, member) || adding_the_creator_as_owner_in_a_personal_project?
|
||||
end
|
||||
|
||||
def adding_a_new_owner?
|
||||
def adding_the_creator_as_owner_in_a_personal_project?
|
||||
# this condition is reached during testing setup a lot due to use of `.add_user`
|
||||
member.owner? && member.new_record?
|
||||
member.project.personal_namespace_holder?(member.user) && member.new_record?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,4 +2,4 @@
|
||||
|
||||
# Explicitly set the JSON adapter used by MultiJson
|
||||
# Currently we want this to default to the existing json gem
|
||||
MultiJson.use(:json_gem)
|
||||
MultiJson.use(:oj)
|
||||
|
@ -4,8 +4,8 @@ class Gitlab::Seeder::Users
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
RANDOM_USERS_COUNT = 20
|
||||
MASS_NAMESPACES_COUNT = 100
|
||||
MASS_USERS_COUNT = ENV['CI'] ? 10 : 1_000_000
|
||||
|
||||
attr_reader :opts
|
||||
|
||||
def initialize(opts = {})
|
||||
@ -15,6 +15,7 @@ class Gitlab::Seeder::Users
|
||||
def seed!
|
||||
Sidekiq::Testing.inline! do
|
||||
create_mass_users!
|
||||
create_mass_namespaces!
|
||||
create_random_users!
|
||||
end
|
||||
end
|
||||
@ -26,20 +27,22 @@ class Gitlab::Seeder::Users
|
||||
|
||||
Gitlab::Seeder.with_mass_insert(MASS_USERS_COUNT, User) do
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO users (username, name, email, confirmed_at, projects_limit, encrypted_password)
|
||||
INSERT INTO users (username, name, email, state, confirmed_at, projects_limit, encrypted_password)
|
||||
SELECT
|
||||
'#{Gitlab::Seeder::MASS_INSERT_USER_START}' || seq,
|
||||
'Seed user ' || seq,
|
||||
'seed_user' || seq || '@example.com',
|
||||
'active',
|
||||
to_timestamp(seq),
|
||||
#{MASS_USERS_COUNT},
|
||||
'#{encrypted_password}'
|
||||
FROM generate_series(1, #{MASS_USERS_COUNT}) AS seq
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
relation = User.where(admin: false)
|
||||
Gitlab::Seeder.with_mass_insert(relation.count, Namespace) do
|
||||
Gitlab::Seeder.with_mass_insert(relation.count, 'user namespaces') do
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO namespaces (name, path, owner_id, type)
|
||||
SELECT
|
||||
@ -48,6 +51,16 @@ class Gitlab::Seeder::Users
|
||||
id,
|
||||
'User'
|
||||
FROM users WHERE NOT admin
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
Gitlab::Seeder.with_mass_insert(relation.count, "User namespaces routes") do
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO routes (namespace_id, source_id, source_type, path, name)
|
||||
SELECT id as namespace_id, id as source_id, 'Namespace', path, name
|
||||
FROM namespaces WHERE type IS NULL OR type = 'User'
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
@ -74,6 +87,97 @@ class Gitlab::Seeder::Users
|
||||
end
|
||||
end
|
||||
|
||||
def create_mass_namespaces!
|
||||
Gitlab::Seeder.with_mass_insert(MASS_NAMESPACES_COUNT, "root namespaces and subgroups 9 levels deep") do
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO namespaces (name, path, type)
|
||||
SELECT
|
||||
'mass insert group level 0 - ' || seq,
|
||||
'#{Gitlab::Seeder::MASS_INSERT_GROUP_START}_0_' || seq,
|
||||
'Group'
|
||||
FROM generate_series(1, #{MASS_NAMESPACES_COUNT}) AS seq
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
|
||||
(1..9).each do |idx|
|
||||
count = Namespace.where("path LIKE '#{Gitlab::Seeder::MASS_INSERT_PREFIX}%'").where(type: 'Group').count * 2
|
||||
Gitlab::Seeder.log_message("Creating subgroups at level #{idx}: #{count}")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO namespaces (name, path, type, parent_id)
|
||||
SELECT
|
||||
'mass insert group level #{idx} - ' || seq,
|
||||
'#{Gitlab::Seeder::MASS_INSERT_GROUP_START}_#{idx}_' || seq,
|
||||
'Group',
|
||||
namespaces.id
|
||||
FROM namespaces
|
||||
CROSS JOIN generate_series(1, 2) AS seq
|
||||
WHERE namespaces.type='Group' AND namespaces.path like '#{Gitlab::Seeder::MASS_INSERT_GROUP_START}_#{idx-1}_%'
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
Gitlab::Seeder.log_message("creating routes.")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
WITH RECURSIVE cte(source_id, namespace_id, parent_id, path, height) AS (
|
||||
(
|
||||
SELECT ARRAY[batch.id], batch.id, batch.parent_id, batch.path, 1
|
||||
FROM
|
||||
"namespaces" as batch
|
||||
WHERE
|
||||
"batch"."type" = 'Group' AND "batch"."parent_id" is null
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT array_append(cte.source_id, n.id), n.id, n.parent_id, cte.path || '/' || n.path, cte.height+1
|
||||
FROM
|
||||
"namespaces" as n,
|
||||
"cte"
|
||||
WHERE
|
||||
"n"."type" = 'Group'
|
||||
AND "n"."parent_id" = "cte"."namespace_id"
|
||||
)
|
||||
)
|
||||
INSERT INTO routes (namespace_id, source_id, source_type, path, name)
|
||||
SELECT cte.namespace_id as namespace_id, cte.namespace_id as source_id, 'Namespace', cte.path, cte.path FROM cte
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
|
||||
Gitlab::Seeder.log_message("filling traversal ids.")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
WITH RECURSIVE cte(source_id, namespace_id, parent_id) AS (
|
||||
(
|
||||
SELECT ARRAY[batch.id], batch.id, batch.parent_id
|
||||
FROM
|
||||
"namespaces" as batch
|
||||
WHERE
|
||||
"batch"."type" = 'Group' AND "batch"."parent_id" is null
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT array_append(cte.source_id, n.id), n.id, n.parent_id
|
||||
FROM
|
||||
"namespaces" as n,
|
||||
"cte"
|
||||
WHERE
|
||||
"n"."type" = 'Group'
|
||||
AND "n"."parent_id" = "cte"."namespace_id"
|
||||
)
|
||||
)
|
||||
UPDATE namespaces
|
||||
SET traversal_ids = computed.source_id FROM (SELECT namespace_id, source_id FROM cte) AS computed
|
||||
where computed.namespace_id = namespaces.id AND namespaces.path LIKE '#{Gitlab::Seeder::MASS_INSERT_PREFIX}%'
|
||||
SQL
|
||||
|
||||
Gitlab::Seeder.log_message("creating namespace settings.")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO namespace_settings(namespace_id, created_at, updated_at)
|
||||
SELECT id, now(), now() FROM namespaces
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def random_password
|
||||
@random_password ||= SecureRandom.hex.slice(0,16)
|
||||
end
|
||||
|
@ -53,14 +53,56 @@ class Gitlab::Seeder::Projects
|
||||
public: 1 # 1m projects = 5m total
|
||||
}
|
||||
|
||||
BATCH_SIZE = 100_000
|
||||
|
||||
def seed!
|
||||
Sidekiq::Testing.inline! do
|
||||
create_real_projects!
|
||||
create_large_projects!
|
||||
create_mass_projects!
|
||||
end
|
||||
end
|
||||
|
||||
def self.insert_project_namespaces_sql(type:, range:)
|
||||
<<~SQL
|
||||
INSERT INTO namespaces (name, path, parent_id, owner_id, type, visibility_level, created_at, updated_at)
|
||||
SELECT
|
||||
'Seed project ' || seq || ' ' || ('{#{Gitlab::Seeder::Projects.visibility_per_user}}'::text[])[seq] AS project_name,
|
||||
'#{Gitlab::Seeder::MASS_INSERT_PROJECT_START}' || ('{#{Gitlab::Seeder::Projects.visibility_per_user}}'::text[])[seq] || '_' || seq AS namespace_path,
|
||||
n.id AS parent_id,
|
||||
n.owner_id AS owner_id,
|
||||
'Project' AS type,
|
||||
('{#{Gitlab::Seeder::Projects.visibility_level_per_user}}'::int[])[seq] AS visibility_level,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at
|
||||
FROM namespaces n
|
||||
CROSS JOIN generate_series(1, #{Gitlab::Seeder::Projects.projects_per_user_count}) AS seq
|
||||
WHERE type='#{type}' AND path LIKE '#{Gitlab::Seeder::MASS_INSERT_PREFIX}%'
|
||||
AND n.id BETWEEN #{range.first} AND #{range.last}
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.insert_projects_sql(type:, range:)
|
||||
<<~SQL
|
||||
INSERT INTO projects (name, path, creator_id, namespace_id, project_namespace_id, visibility_level, created_at, updated_at)
|
||||
SELECT
|
||||
n.name AS project_name,
|
||||
n.path AS project_path,
|
||||
n.owner_id AS creator_id,
|
||||
n.parent_id AS namespace_id,
|
||||
n.id AS project_namespace_id,
|
||||
n.visibility_level AS visibility_level,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at
|
||||
FROM namespaces n
|
||||
WHERE type = 'Project' AND n.parent_id IN (
|
||||
SELECT id FROM namespaces n1 WHERE type='#{type}'
|
||||
AND path LIKE '#{Gitlab::Seeder::MASS_INSERT_PREFIX}%' AND n1.id BETWEEN #{range.first} AND #{range.last}
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_real_projects!
|
||||
@ -156,55 +198,26 @@ class Gitlab::Seeder::Projects
|
||||
end
|
||||
end
|
||||
|
||||
def create_mass_projects!
|
||||
projects_per_user_count = MASS_PROJECTS_COUNT_PER_USER.values.sum
|
||||
visibility_per_user = ['private'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:private) +
|
||||
['internal'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:internal) +
|
||||
['public'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:public)
|
||||
visibility_level_per_user = visibility_per_user.map { |visibility| Gitlab::VisibilityLevel.level_value(visibility) }
|
||||
def self.projects_per_user_count
|
||||
MASS_PROJECTS_COUNT_PER_USER.values.sum
|
||||
end
|
||||
|
||||
visibility_per_user = visibility_per_user.join(',')
|
||||
visibility_level_per_user = visibility_level_per_user.join(',')
|
||||
def self.visibility_per_user_array
|
||||
['private'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:private) +
|
||||
['internal'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:internal) +
|
||||
['public'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:public)
|
||||
end
|
||||
|
||||
Gitlab::Seeder.with_mass_insert(User.count * projects_per_user_count, "Projects and relations") do
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO projects (name, path, creator_id, namespace_id, visibility_level, created_at, updated_at)
|
||||
SELECT
|
||||
'Seed project ' || seq || ' ' || ('{#{visibility_per_user}}'::text[])[seq] AS project_name,
|
||||
'#{Gitlab::Seeder::MASS_INSERT_PROJECT_START}' || ('{#{visibility_per_user}}'::text[])[seq] || '_' || seq AS project_path,
|
||||
u.id AS user_id,
|
||||
n.id AS namespace_id,
|
||||
('{#{visibility_level_per_user}}'::int[])[seq] AS visibility_level,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at
|
||||
FROM users u
|
||||
CROSS JOIN generate_series(1, #{projects_per_user_count}) AS seq
|
||||
JOIN namespaces n ON n.owner_id=u.id
|
||||
SQL
|
||||
def self.visibility_level_per_user_map
|
||||
visibility_per_user_array.map { |visibility| Gitlab::VisibilityLevel.level_value(visibility) }
|
||||
end
|
||||
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO project_features (project_id, merge_requests_access_level, issues_access_level, wiki_access_level,
|
||||
pages_access_level)
|
||||
SELECT
|
||||
id,
|
||||
#{ProjectFeature::ENABLED} AS merge_requests_access_level,
|
||||
#{ProjectFeature::ENABLED} AS issues_access_level,
|
||||
#{ProjectFeature::ENABLED} AS wiki_access_level,
|
||||
#{ProjectFeature::ENABLED} AS pages_access_level
|
||||
FROM projects ON CONFLICT (project_id) DO NOTHING;
|
||||
SQL
|
||||
def self.visibility_per_user
|
||||
visibility_per_user_array.join(',')
|
||||
end
|
||||
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO routes (source_id, source_type, name, path)
|
||||
SELECT
|
||||
p.id,
|
||||
'Project',
|
||||
u.name || ' / ' || p.name,
|
||||
u.username || '/' || p.path
|
||||
FROM projects p JOIN users u ON u.id=p.creator_id
|
||||
ON CONFLICT (source_type, source_id) DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
def self.visibility_level_per_user
|
||||
visibility_level_per_user_map.join(',')
|
||||
end
|
||||
end
|
||||
|
||||
|
31
db/fixtures/development/03_project_1_user_projects.rb
Normal file
31
db/fixtures/development/03_project_1_user_projects.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Gitlab::Seeder::UserProjects
|
||||
def seed!
|
||||
create_user_projects!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_user_projects!
|
||||
user_namespaces = Namespace.where("path LIKE ?", "#{Gitlab::Seeder::MASS_INSERT_PREFIX}%").where(type: 'User')
|
||||
|
||||
Gitlab::Seeder.with_mass_insert(user_namespaces.count * Gitlab::Seeder::Projects.projects_per_user_count, "User projects and corresponding project namespaces") do
|
||||
user_namespaces.each_batch(of: Gitlab::Seeder::Projects::BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
count = index * batch.size * Gitlab::Seeder::Projects.projects_per_user_count
|
||||
|
||||
Gitlab::Seeder.log_message("Creating project namespaces: #{count}.")
|
||||
ActiveRecord::Base.connection.execute(Gitlab::Seeder::Projects.insert_project_namespaces_sql(type: 'User', range: range))
|
||||
|
||||
Gitlab::Seeder.log_message("Creating projects: #{count}.")
|
||||
ActiveRecord::Base.connection.execute(Gitlab::Seeder::Projects.insert_projects_sql(type: 'User', range: range))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
projects = Gitlab::Seeder::UserProjects.new
|
||||
projects.seed!
|
||||
end
|
31
db/fixtures/development/03_project_2_group_projects.rb
Normal file
31
db/fixtures/development/03_project_2_group_projects.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Gitlab::Seeder::GroupProjects
|
||||
def seed!
|
||||
create_projects!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_projects!
|
||||
groups = Namespace.where("path LIKE ?", "#{Gitlab::Seeder::MASS_INSERT_PREFIX}%").where(type: 'Group')
|
||||
|
||||
Gitlab::Seeder.with_mass_insert(groups.count * Gitlab::Seeder::Projects.projects_per_user_count, "Projects and corresponding project namespaces") do
|
||||
groups.each_batch(of: Gitlab::Seeder::Projects::BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
count = index * batch.size * Gitlab::Seeder::Projects.projects_per_user_count
|
||||
|
||||
Gitlab::Seeder.log_message("Creating projects namespaces: #{count}.")
|
||||
ActiveRecord::Base.connection.execute(Gitlab::Seeder::Projects.insert_project_namespaces_sql(type: 'Group', range: range))
|
||||
|
||||
Gitlab::Seeder.log_message("Creating projects: #{count}.")
|
||||
ActiveRecord::Base.connection.execute(Gitlab::Seeder::Projects.insert_projects_sql(type: 'Group', range: range))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
projects = Gitlab::Seeder::GroupProjects.new
|
||||
projects.seed!
|
||||
end
|
39
db/fixtures/development/03_project_3_features.rb
Normal file
39
db/fixtures/development/03_project_3_features.rb
Normal file
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Gitlab::Seeder::ProjectFeatures
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
BATCH_SIZE = 100_000
|
||||
|
||||
def seed!
|
||||
create_project_features!
|
||||
end
|
||||
|
||||
def create_project_features!
|
||||
Gitlab::Seeder.with_mass_insert(Project.count, "Project features") do
|
||||
Project.each_batch(of: BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
count = index * BATCH_SIZE
|
||||
|
||||
Gitlab::Seeder.log_message("Creating project features: #{count}.")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO project_features (project_id, merge_requests_access_level, issues_access_level, wiki_access_level, pages_access_level)
|
||||
SELECT
|
||||
id,
|
||||
#{ProjectFeature::ENABLED} AS merge_requests_access_level,
|
||||
#{ProjectFeature::ENABLED} AS issues_access_level,
|
||||
#{ProjectFeature::ENABLED} AS wiki_access_level,
|
||||
#{ProjectFeature::ENABLED} AS pages_access_level
|
||||
FROM projects
|
||||
WHERE projects.id BETWEEN #{range.first} AND #{range.last}
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
projects = Gitlab::Seeder::ProjectFeatures.new
|
||||
projects.seed!
|
||||
end
|
40
db/fixtures/development/03_project_4_routes.rb
Normal file
40
db/fixtures/development/03_project_4_routes.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Gitlab::Seeder::ProjectRoutes
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
BATCH_SIZE = 100_000
|
||||
|
||||
def seed!
|
||||
create_project_routes!
|
||||
end
|
||||
|
||||
def create_project_routes!
|
||||
Gitlab::Seeder.with_mass_insert(Project.count, "Project routes") do
|
||||
Project.each_batch(of: BATCH_SIZE / 2) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
count = index * BATCH_SIZE / 2
|
||||
|
||||
Gitlab::Seeder.log_message("Creating project routes: #{count}.")
|
||||
ActiveRecord::Base.connection.execute <<~SQL
|
||||
INSERT INTO routes (namespace_id, source_id, source_type, name, path)
|
||||
SELECT
|
||||
p.project_namespace_id as namespace_id,
|
||||
p.id as source_id,
|
||||
'Project',
|
||||
routes.name || ' / ' || p.name,
|
||||
routes.path || '/' || p.path
|
||||
FROM projects p
|
||||
INNER JOIN routes ON routes.source_id = p.namespace_id and source_type = 'Namespace'
|
||||
WHERE p.id BETWEEN #{range.first} AND #{range.last}
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
projects = Gitlab::Seeder::ProjectRoutes.new
|
||||
projects.seed!
|
||||
end
|
@ -37,13 +37,15 @@ class Gitlab::Seeder::ProjectLabels
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
puts "\nGenerating group labels"
|
||||
Group.all.find_each do |group|
|
||||
Gitlab::Seeder::GroupLabels.new(group).seed!
|
||||
label_per_group = 10
|
||||
puts "\nGenerating group labels: #{Group.not_mass_generated.count * label_per_group}"
|
||||
Group.not_mass_generated.find_each do |group|
|
||||
Gitlab::Seeder::GroupLabels.new(group, label_per_group: label_per_group).seed!
|
||||
end
|
||||
|
||||
puts "\nGenerating project labels"
|
||||
label_per_project = 5
|
||||
puts "\nGenerating project labels: #{Project.not_mass_generated.count * label_per_project}"
|
||||
Project.not_mass_generated.find_each do |project|
|
||||
Gitlab::Seeder::ProjectLabels.new(project).seed!
|
||||
Gitlab::Seeder::ProjectLabels.new(project, label_per_project: label_per_project).seed!
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ require './spec/support/sidekiq_middleware'
|
||||
|
||||
Sidekiq::Testing.inline! do
|
||||
Gitlab::Seeder.quiet do
|
||||
Group.all.each do |group|
|
||||
Group.not_mass_generated.each do |group|
|
||||
User.not_mass_generated.sample(4).each do |user|
|
||||
if group.add_user(user, Gitlab::Access.values.sample).persisted?
|
||||
print '.'
|
||||
|
@ -41,7 +41,7 @@ end
|
||||
Gitlab::Seeder.quiet do
|
||||
puts "\nGenerating group crm organizations and contacts"
|
||||
|
||||
Group.where('parent_id IS NULL').first(10).each do |group|
|
||||
Group.not_mass_generated.where('parent_id IS NULL').first(10).each do |group|
|
||||
Gitlab::Seeder::Crm.new(group).seed!
|
||||
end
|
||||
end
|
||||
|
@ -39,6 +39,9 @@ To install the agent in your cluster:
|
||||
|
||||
You must register an agent with GitLab.
|
||||
|
||||
FLAG:
|
||||
In GitLab 14.10, a [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- For a [GitLab CI/CD workflow](../ci_cd_tunnel.md), ensure that
|
||||
@ -48,8 +51,7 @@ To register an agent with GitLab:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. From the left sidebar, select **Infrastructure > Kubernetes clusters**.
|
||||
1. Select **Actions**.
|
||||
1. From the **Select an agent** dropdown list:
|
||||
1. Select **Connect a cluster (agent)**.
|
||||
- If you want to create a configuration with CI/CD defaults, type a name for the agent.
|
||||
- If you already have an [agent configuration file](#create-an-agent-configuration-file), select it from the list.
|
||||
1. Select **Register an agent**.
|
||||
|
@ -48,10 +48,13 @@ This project provides you with:
|
||||
|
||||
## Register the agent
|
||||
|
||||
FLAG:
|
||||
In GitLab 14.10, a [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
|
||||
|
||||
To create a GitLab agent for Kubernetes:
|
||||
|
||||
1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.
|
||||
1. Select **Actions**.
|
||||
1. Select **Connect a cluster (agent)**.
|
||||
1. From the **Select an agent** dropdown list, select `eks-agent` and select **Register an agent**.
|
||||
1. GitLab generates a registration token for the agent. Securely store this secret token, as you will need it later.
|
||||
1. GitLab provides an address for the agent server (KAS), which you will also need later.
|
||||
|
@ -48,10 +48,13 @@ with defaults for name, location, node count, and Kubernetes version.
|
||||
|
||||
## Register the agent
|
||||
|
||||
FLAG:
|
||||
In GitLab 14.10, a [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
|
||||
|
||||
To create a GitLab agent for Kubernetes:
|
||||
|
||||
1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.
|
||||
1. Select **Actions**.
|
||||
1. Select **Connect a cluster (agent)**.
|
||||
1. From the **Select an agent** dropdown list, select `gke-agent` and select **Register an agent**.
|
||||
1. GitLab generates a registration token for the agent. Securely store this secret token, as you will need it later.
|
||||
1. GitLab provides an address for the agent server (KAS), which you will also need later.
|
||||
|
@ -6,7 +6,7 @@ module API
|
||||
integrations = Helpers::IntegrationsHelpers.integrations
|
||||
integration_classes = Helpers::IntegrationsHelpers.integration_classes
|
||||
|
||||
if Rails.env.development?
|
||||
if Gitlab.dev_or_test_env?
|
||||
integrations['mock-ci'] = [
|
||||
{
|
||||
required: true,
|
||||
|
@ -4,12 +4,24 @@ module Gitlab
|
||||
class Seeder
|
||||
extend ActionView::Helpers::NumberHelper
|
||||
|
||||
MASS_INSERT_PROJECT_START = 'mass_insert_project_'
|
||||
MASS_INSERT_USER_START = 'mass_insert_user_'
|
||||
MASS_INSERT_PREFIX = 'mass_insert'
|
||||
MASS_INSERT_PROJECT_START = "#{MASS_INSERT_PREFIX}_project_"
|
||||
MASS_INSERT_GROUP_START = "#{MASS_INSERT_PREFIX}_group_"
|
||||
MASS_INSERT_USER_START = "#{MASS_INSERT_PREFIX}_user_"
|
||||
REPORTED_USER_START = 'reported_user_'
|
||||
ESTIMATED_INSERT_PER_MINUTE = 2_000_000
|
||||
ESTIMATED_INSERT_PER_MINUTE = 250_000
|
||||
MASS_INSERT_ENV = 'MASS_INSERT'
|
||||
|
||||
module NamespaceSeed
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :not_mass_generated, -> do
|
||||
where.not("path LIKE '#{MASS_INSERT_GROUP_START}%'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ProjectSeed
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
@ -30,6 +42,10 @@ module Gitlab
|
||||
end
|
||||
end
|
||||
|
||||
def self.log_message(message)
|
||||
puts "#{Time.current}: #{message}"
|
||||
end
|
||||
|
||||
def self.with_mass_insert(size, model)
|
||||
humanized_model_name = model.is_a?(String) ? model : model.model_name.human.pluralize(size)
|
||||
|
||||
@ -63,6 +79,7 @@ module Gitlab
|
||||
|
||||
def self.quiet
|
||||
# Additional seed logic for models.
|
||||
Namespace.include(NamespaceSeed)
|
||||
Project.include(ProjectSeed)
|
||||
User.include(UserSeed)
|
||||
|
||||
|
@ -10,7 +10,12 @@ namespace :dev do
|
||||
|
||||
Gitlab::Database::EachDatabase.each_database_connection do |connection|
|
||||
# Make sure DB statistics are up to date.
|
||||
# gitlab:setup task can insert quite a bit of data, especially with MASS_INSERT=1
|
||||
# so ANALYZE can take more than default 15s statement timeout. This being a dev task,
|
||||
# we disable the statement timeout for ANALYZE to run and enable it back afterwards.
|
||||
connection.execute('SET statement_timeout TO 0')
|
||||
connection.execute('ANALYZE')
|
||||
connection.execute('RESET statement_timeout')
|
||||
end
|
||||
|
||||
Rake::Task["gitlab:shell:setup"].invoke
|
||||
|
@ -31,12 +31,17 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatNumber(12.345, 4)).toBe('12.3450');
|
||||
});
|
||||
|
||||
it('formats a large integer with a length limit', () => {
|
||||
it('formats a large integer with a max length - using legacy positional argument', () => {
|
||||
expect(formatNumber(10 ** 7, undefined)).toBe('10,000,000');
|
||||
expect(formatNumber(10 ** 7, undefined, 9)).toBe('1.00e+7');
|
||||
expect(formatNumber(10 ** 7, undefined, 10)).toBe('10,000,000');
|
||||
});
|
||||
|
||||
it('formats a large integer with a max length', () => {
|
||||
expect(formatNumber(10 ** 7, undefined, { maxLength: 9 })).toBe('1.00e+7');
|
||||
expect(formatNumber(10 ** 7, undefined, { maxLength: 10 })).toBe('10,000,000');
|
||||
});
|
||||
|
||||
describe('formats with a different locale', () => {
|
||||
let originalLang;
|
||||
|
||||
@ -92,7 +97,7 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatSuffix(-1000000)).toBe('-1,000,000pop.');
|
||||
});
|
||||
|
||||
it('formats a floating point nugative number', () => {
|
||||
it('formats a floating point negative number', () => {
|
||||
expect(formatSuffix(-0.1)).toBe('-0.1pop.');
|
||||
expect(formatSuffix(-0.1, 0)).toBe('-0pop.');
|
||||
expect(formatSuffix(-0.1, 2)).toBe('-0.10pop.');
|
||||
@ -108,10 +113,20 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatSuffix(10 ** 10)).toBe('10,000,000,000pop.');
|
||||
});
|
||||
|
||||
it('formats a large integer with a length limit', () => {
|
||||
it('formats using a unit separator', () => {
|
||||
expect(formatSuffix(10, 0, { unitSeparator: ' ' })).toBe('10 pop.');
|
||||
expect(formatSuffix(10, 0, { unitSeparator: ' x ' })).toBe('10 x pop.');
|
||||
});
|
||||
|
||||
it('formats a large integer with a max length - using legacy positional argument', () => {
|
||||
expect(formatSuffix(10 ** 7, undefined, 10)).toBe('1.00e+7pop.');
|
||||
expect(formatSuffix(10 ** 10, undefined, 10)).toBe('1.00e+10pop.');
|
||||
});
|
||||
|
||||
it('formats a large integer with a max length', () => {
|
||||
expect(formatSuffix(10 ** 7, undefined, { maxLength: 10 })).toBe('1.00e+7pop.');
|
||||
expect(formatSuffix(10 ** 10, undefined, { maxLength: 10 })).toBe('1.00e+10pop.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scaledSIFormatter', () => {
|
||||
@ -143,6 +158,10 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatGibibytes(10 ** 10)).toBe('10GB');
|
||||
expect(formatGibibytes(10 ** 11)).toBe('100GB');
|
||||
});
|
||||
|
||||
it('formats bytes using a unit separator', () => {
|
||||
expect(formatGibibytes(1, 0, { unitSeparator: ' ' })).toBe('1 B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scaled format with offset', () => {
|
||||
@ -174,6 +193,19 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatGigaBytes(10 ** 9)).toBe('1EB');
|
||||
});
|
||||
|
||||
it('formats bytes using a unit separator', () => {
|
||||
expect(formatGigaBytes(1, undefined, { unitSeparator: ' ' })).toBe('1 GB');
|
||||
});
|
||||
|
||||
it('formats long byte numbers with max length - using legacy positional argument', () => {
|
||||
expect(formatGigaBytes(1, 8, 7)).toBe('1.00e+0GB');
|
||||
});
|
||||
|
||||
it('formats long byte numbers with max length', () => {
|
||||
expect(formatGigaBytes(1, 8)).toBe('1.00000000GB');
|
||||
expect(formatGigaBytes(1, 8, { maxLength: 7 })).toBe('1.00e+0GB');
|
||||
});
|
||||
|
||||
it('formatting of too large numbers is not suported', () => {
|
||||
// formatting YB is out of range
|
||||
expect(() => scaledSIFormatter('B', 9)).toThrow();
|
||||
@ -216,6 +248,10 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatMilligrams(-100)).toBe('-100mg');
|
||||
expect(formatMilligrams(-(10 ** 4))).toBe('-10g');
|
||||
});
|
||||
|
||||
it('formats using a unit separator', () => {
|
||||
expect(formatMilligrams(1, undefined, { unitSeparator: ' ' })).toBe('1 mg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -253,6 +289,10 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatScaledBin(10 * 1024 ** 3)).toBe('10GiB');
|
||||
expect(formatScaledBin(100 * 1024 ** 3)).toBe('100GiB');
|
||||
});
|
||||
|
||||
it('formats using a unit separator', () => {
|
||||
expect(formatScaledBin(1, undefined, { unitSeparator: ' ' })).toBe('1 B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scaled format with offset', () => {
|
||||
@ -288,6 +328,10 @@ describe('unit_format/formatter_factory', () => {
|
||||
expect(formatGibibytes(100 * 1024 ** 3)).toBe('100EiB');
|
||||
});
|
||||
|
||||
it('formats using a unit separator', () => {
|
||||
expect(formatGibibytes(1, undefined, { unitSeparator: ' ' })).toBe('1 GiB');
|
||||
});
|
||||
|
||||
it('formatting of too large numbers is not suported', () => {
|
||||
// formatting YB is out of range
|
||||
expect(() => scaledBinaryFormatter('B', 9)).toThrow();
|
||||
|
@ -74,10 +74,13 @@ describe('unit_format', () => {
|
||||
|
||||
it('seconds', () => {
|
||||
expect(seconds(1)).toBe('1s');
|
||||
expect(seconds(1, undefined, { unitSeparator: ' ' })).toBe('1 s');
|
||||
});
|
||||
|
||||
it('milliseconds', () => {
|
||||
expect(milliseconds(1)).toBe('1ms');
|
||||
expect(milliseconds(1, undefined, { unitSeparator: ' ' })).toBe('1 ms');
|
||||
|
||||
expect(milliseconds(100)).toBe('100ms');
|
||||
expect(milliseconds(1000)).toBe('1,000ms');
|
||||
expect(milliseconds(10_000)).toBe('10,000ms');
|
||||
@ -87,6 +90,7 @@ describe('unit_format', () => {
|
||||
it('decimalBytes', () => {
|
||||
expect(decimalBytes(1)).toBe('1B');
|
||||
expect(decimalBytes(1, 1)).toBe('1.0B');
|
||||
expect(decimalBytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 B');
|
||||
|
||||
expect(decimalBytes(10)).toBe('10B');
|
||||
expect(decimalBytes(10 ** 2)).toBe('100B');
|
||||
@ -104,31 +108,37 @@ describe('unit_format', () => {
|
||||
it('kilobytes', () => {
|
||||
expect(kilobytes(1)).toBe('1kB');
|
||||
expect(kilobytes(1, 1)).toBe('1.0kB');
|
||||
expect(kilobytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 kB');
|
||||
});
|
||||
|
||||
it('megabytes', () => {
|
||||
expect(megabytes(1)).toBe('1MB');
|
||||
expect(megabytes(1, 1)).toBe('1.0MB');
|
||||
expect(megabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 MB');
|
||||
});
|
||||
|
||||
it('gigabytes', () => {
|
||||
expect(gigabytes(1)).toBe('1GB');
|
||||
expect(gigabytes(1, 1)).toBe('1.0GB');
|
||||
expect(gigabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 GB');
|
||||
});
|
||||
|
||||
it('terabytes', () => {
|
||||
expect(terabytes(1)).toBe('1TB');
|
||||
expect(terabytes(1, 1)).toBe('1.0TB');
|
||||
expect(terabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 TB');
|
||||
});
|
||||
|
||||
it('petabytes', () => {
|
||||
expect(petabytes(1)).toBe('1PB');
|
||||
expect(petabytes(1, 1)).toBe('1.0PB');
|
||||
expect(petabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 PB');
|
||||
});
|
||||
|
||||
it('bytes', () => {
|
||||
expect(bytes(1)).toBe('1B');
|
||||
expect(bytes(1, 1)).toBe('1.0B');
|
||||
expect(bytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 B');
|
||||
|
||||
expect(bytes(10)).toBe('10B');
|
||||
expect(bytes(100)).toBe('100B');
|
||||
@ -142,26 +152,31 @@ describe('unit_format', () => {
|
||||
it('kibibytes', () => {
|
||||
expect(kibibytes(1)).toBe('1KiB');
|
||||
expect(kibibytes(1, 1)).toBe('1.0KiB');
|
||||
expect(kibibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 KiB');
|
||||
});
|
||||
|
||||
it('mebibytes', () => {
|
||||
expect(mebibytes(1)).toBe('1MiB');
|
||||
expect(mebibytes(1, 1)).toBe('1.0MiB');
|
||||
expect(mebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 MiB');
|
||||
});
|
||||
|
||||
it('gibibytes', () => {
|
||||
expect(gibibytes(1)).toBe('1GiB');
|
||||
expect(gibibytes(1, 1)).toBe('1.0GiB');
|
||||
expect(gibibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 GiB');
|
||||
});
|
||||
|
||||
it('tebibytes', () => {
|
||||
expect(tebibytes(1)).toBe('1TiB');
|
||||
expect(tebibytes(1, 1)).toBe('1.0TiB');
|
||||
expect(tebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 TiB');
|
||||
});
|
||||
|
||||
it('pebibytes', () => {
|
||||
expect(pebibytes(1)).toBe('1PiB');
|
||||
expect(pebibytes(1, 1)).toBe('1.0PiB');
|
||||
expect(pebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 PiB');
|
||||
});
|
||||
|
||||
describe('getFormatter', () => {
|
||||
|
@ -3,6 +3,24 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Seeder do
|
||||
describe Namespace do
|
||||
subject { described_class }
|
||||
|
||||
it 'has not_mass_generated scope' do
|
||||
expect { Namespace.not_mass_generated }.to raise_error(NoMethodError)
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
expect { Namespace.not_mass_generated }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes NamespaceSeed module' do
|
||||
Gitlab::Seeder.quiet do
|
||||
is_expected.to include_module(Gitlab::Seeder::NamespaceSeed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.quiet' do
|
||||
let(:database_base_models) do
|
||||
{
|
||||
@ -50,4 +68,13 @@ RSpec.describe Gitlab::Seeder do
|
||||
notification_service.new_note(note)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.log_message' do
|
||||
it 'prepends timestamp to the logged message' do
|
||||
freeze_time do
|
||||
message = "some message."
|
||||
expect { described_class.log_message(message) }.to output(/#{Time.current}: #{message}/).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -78,9 +78,10 @@ RSpec.describe Integrations::EmailsOnPush do
|
||||
end
|
||||
|
||||
describe '.valid_recipients' do
|
||||
let(:recipients) { '<invalid> foobar Valid@recipient.com Dup@lica.te dup@lica.te Dup@Lica.te' }
|
||||
let(:recipients) { '<invalid> foobar valid@dup@asd Valid@recipient.com Dup@lica.te dup@lica.te Dup@Lica.te' }
|
||||
|
||||
it 'removes invalid email addresses and removes duplicates by keeping the original capitalization' do
|
||||
expect(described_class.valid_recipients(recipients)).not_to contain_exactly('valid@dup@asd')
|
||||
expect(described_class.valid_recipients(recipients)).to contain_exactly('Valid@recipient.com', 'Dup@lica.te')
|
||||
end
|
||||
end
|
||||
|
@ -726,6 +726,33 @@ RSpec.describe Project, factory_default: :keep do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#personal_namespace_holder?' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:namespace_user) { create(:user) }
|
||||
let_it_be(:admin_user) { create(:user, :admin) }
|
||||
let_it_be(:personal_project) { create(:project, namespace: namespace_user.namespace) }
|
||||
let_it_be(:group_project) { create(:project, group: group) }
|
||||
let_it_be(:another_user) { create(:user) }
|
||||
let_it_be(:group_owner_user) { create(:user).tap { |user| group.add_owner(user) } }
|
||||
|
||||
where(:project, :user, :result) do
|
||||
ref(:personal_project) | ref(:namespace_user) | true
|
||||
ref(:personal_project) | ref(:admin_user) | false
|
||||
ref(:personal_project) | ref(:another_user) | false
|
||||
ref(:personal_project) | nil | false
|
||||
ref(:group_project) | ref(:namespace_user) | false
|
||||
ref(:group_project) | ref(:group_owner_user) | false
|
||||
ref(:group_project) | ref(:another_user) | false
|
||||
ref(:group_project) | nil | false
|
||||
ref(:group_project) | nil | false
|
||||
ref(:group_project) | ref(:admin_user) | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { expect(project.personal_namespace_holder?(user)).to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#default_pipeline_lock' do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
|
||||
|
@ -17,7 +17,9 @@ RSpec.describe 'dev rake tasks' do
|
||||
it 'sets up the development environment', :aggregate_failures do
|
||||
expect(Rake::Task['gitlab:setup']).to receive(:invoke)
|
||||
|
||||
expect(connections).to all(receive(:execute).with('SET statement_timeout TO 0'))
|
||||
expect(connections).to all(receive(:execute).with('ANALYZE'))
|
||||
expect(connections).to all(receive(:execute).with('RESET statement_timeout'))
|
||||
|
||||
expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
|
||||
|
||||
|
Reference in New Issue
Block a user