Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f5a72705e4
commit
155fb78b9a
|
@ -25,19 +25,19 @@ export default {
|
|||
},
|
||||
stickinessOptions: [
|
||||
{
|
||||
value: 'DEFAULT',
|
||||
value: 'default',
|
||||
text: __('Available ID'),
|
||||
},
|
||||
{
|
||||
value: 'USERID',
|
||||
value: 'userId',
|
||||
text: __('User ID'),
|
||||
},
|
||||
{
|
||||
value: 'SESSIONID',
|
||||
value: 'sessionId',
|
||||
text: __('Session ID'),
|
||||
},
|
||||
{
|
||||
value: 'RANDOM',
|
||||
value: 'random',
|
||||
text: __('Random'),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -37,7 +37,6 @@ module Namespaces
|
|||
module Traversal
|
||||
module Linear
|
||||
extend ActiveSupport::Concern
|
||||
include LinearScopes
|
||||
|
||||
UnboundedSearch = Class.new(StandardError)
|
||||
|
||||
|
@ -45,6 +44,14 @@ module Namespaces
|
|||
before_update :lock_both_roots, if: -> { sync_traversal_ids? && parent_id_changed? }
|
||||
after_create :sync_traversal_ids, if: -> { sync_traversal_ids? }
|
||||
after_update :sync_traversal_ids, if: -> { sync_traversal_ids? && saved_change_to_parent_id? }
|
||||
|
||||
scope :traversal_ids_contains, ->(ids) { where("traversal_ids @> (?)", ids) }
|
||||
# When filtering namespaces by the traversal_ids column to compile a
|
||||
# list of namespace IDs, it's much faster to reference the ID in
|
||||
# traversal_ids than the primary key ID column.
|
||||
# WARNING This scope must be used behind a linear query feature flag
|
||||
# such as `use_traversal_ids`.
|
||||
scope :as_ids, -> { select('traversal_ids[array_length(traversal_ids, 1)] AS id') }
|
||||
end
|
||||
|
||||
def sync_traversal_ids?
|
||||
|
@ -157,14 +164,20 @@ module Namespaces
|
|||
Namespace.lock.select(:id).where(id: roots).order(id: :asc).load
|
||||
end
|
||||
|
||||
# Make sure we drop the STI `type = 'Group'` condition for better performance.
|
||||
# Logically equivalent so long as hierarchies remain homogeneous.
|
||||
def without_sti_condition
|
||||
self.class.unscope(where: :type)
|
||||
end
|
||||
|
||||
# Search this namespace's lineage. Bound inclusively by top node.
|
||||
def lineage(top: nil, bottom: nil, hierarchy_order: nil)
|
||||
raise UnboundedSearch, 'Must bound search by either top or bottom' unless top || bottom
|
||||
|
||||
skope = self.class.without_sti_condition
|
||||
skope = without_sti_condition
|
||||
|
||||
if top
|
||||
skope = skope.where("traversal_ids @> ('{?}')", top.id)
|
||||
skope = skope.traversal_ids_contains("{#{top.id}}")
|
||||
end
|
||||
|
||||
if bottom
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Namespaces
|
||||
module Traversal
|
||||
module LinearScopes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
# When filtering namespaces by the traversal_ids column to compile a
|
||||
# list of namespace IDs, it can be faster to reference the ID in
|
||||
# traversal_ids than the primary key ID column.
|
||||
def as_ids
|
||||
return super unless use_traversal_ids?
|
||||
|
||||
select('namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id')
|
||||
end
|
||||
|
||||
def self_and_descendants
|
||||
return super unless use_traversal_ids?
|
||||
|
||||
without_dups = self_and_descendants_with_duplicates
|
||||
.select('DISTINCT on(namespaces.id) namespaces.*')
|
||||
|
||||
# Wrap the `SELECT DISTINCT on(....)` with a normal query so we
|
||||
# retain expected Rails behavior. Otherwise count and other
|
||||
# aggregates won't work.
|
||||
unscoped.without_sti_condition.from(without_dups, :namespaces)
|
||||
end
|
||||
|
||||
def self_and_descendant_ids
|
||||
return super unless use_traversal_ids?
|
||||
|
||||
self_and_descendants_with_duplicates.select('DISTINCT namespaces.id')
|
||||
end
|
||||
|
||||
# Make sure we drop the STI `type = 'Group'` condition for better performance.
|
||||
# Logically equivalent so long as hierarchies remain homogeneous.
|
||||
def without_sti_condition
|
||||
unscope(where: :type)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_traversal_ids?
|
||||
Feature.enabled?(:use_traversal_ids, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def self_and_descendants_with_duplicates
|
||||
base_ids = select(:id)
|
||||
|
||||
unscoped
|
||||
.without_sti_condition
|
||||
.from("namespaces, (#{base_ids.to_sql}) base")
|
||||
.where('namespaces.traversal_ids @> ARRAY[base.id]')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,6 @@ module Namespaces
|
|||
module Traversal
|
||||
module Recursive
|
||||
extend ActiveSupport::Concern
|
||||
include RecursiveScopes
|
||||
|
||||
def root_ancestor
|
||||
return self if parent.nil?
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Namespaces
|
||||
module Traversal
|
||||
module RecursiveScopes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def as_ids
|
||||
select('id')
|
||||
end
|
||||
|
||||
def self_and_descendants
|
||||
Gitlab::ObjectHierarchy.new(all).base_and_descendants
|
||||
end
|
||||
alias_method :recursive_self_and_descendants, :self_and_descendants
|
||||
|
||||
def self_and_descendant_ids
|
||||
self_and_descendants.as_ids
|
||||
end
|
||||
alias_method :recursive_self_and_descendant_ids, :self_and_descendant_ids
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,7 +16,7 @@ module Operations
|
|||
STRATEGY_USERWITHID => ['userIds'].freeze
|
||||
}.freeze
|
||||
USERID_MAX_LENGTH = 256
|
||||
STICKINESS_SETTINGS = %w[DEFAULT USERID SESSIONID RANDOM].freeze
|
||||
STICKINESS_SETTINGS = %w[default userId sessionId random].freeze
|
||||
|
||||
self.table_name = 'operations_strategies'
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ class ProjectFeature < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
default_value_for(:container_registry_access_level, allows_nil: false) do |feature|
|
||||
default_value_for(:container_registry_access_level) do |feature|
|
||||
if gitlab_config_features.container_registry
|
||||
ENABLED
|
||||
else
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class OperationsFeatureFlagsCorrectFlexibleRolloutValues < ActiveRecord::Migration[6.1]
|
||||
STICKINESS = { "USERID" => "userId", "RANDOM" => "random", "SESSIONID" => "sessionId", "DEFAULT" => "default" }.freeze
|
||||
|
||||
def up
|
||||
STICKINESS.each do |before, after|
|
||||
update_statement = <<-SQL
|
||||
UPDATE operations_strategies
|
||||
SET parameters = parameters || jsonb_build_object('stickiness', '#{quote_string(after)}')
|
||||
WHERE name = 'flexibleRollout' AND parameters->>'stickiness' = '#{quote_string(before)}'
|
||||
SQL
|
||||
|
||||
execute(update_statement)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
STICKINESS.each do |before, after|
|
||||
update_statement = <<-SQL
|
||||
UPDATE operations_strategies
|
||||
SET parameters = parameters || jsonb_build_object('stickiness', '#{quote_string(before)}')
|
||||
WHERE name = 'flexibleRollout' AND parameters->>'stickiness' = '#{quote_string(after)}'
|
||||
SQL
|
||||
|
||||
execute(update_statement)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
cea8e51f6917be9ad43280fba9f8e7d9b9db1f508e249d9f5df792e43c0b8313
|
|
@ -212,7 +212,7 @@
|
|||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"chalk": "^2.4.1",
|
||||
"cheerio": "^1.0.0-rc.9",
|
||||
"commander": "^2.18.0",
|
||||
"commander": "^2.20.3",
|
||||
"custom-jquery-matchers": "^2.1.0",
|
||||
"docdash": "^1.0.2",
|
||||
"eslint": "7.31.0",
|
||||
|
@ -264,7 +264,7 @@
|
|||
"webpack-dev-server": "^3.11.2",
|
||||
"xhr-mock": "^2.5.1",
|
||||
"yarn-check-webpack-plugin": "^1.2.0",
|
||||
"yarn-deduplicate": "^1.1.1"
|
||||
"yarn-deduplicate": "^3.1.0"
|
||||
},
|
||||
"blockedDependencies": {
|
||||
"bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.html#bootstrapvue"
|
||||
|
|
|
@ -84,10 +84,13 @@ module QA
|
|||
# Get issue comments
|
||||
#
|
||||
# @return [Array]
|
||||
def comments(auto_paginate: false)
|
||||
def comments(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_comments_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url)
|
||||
auto_paginated_response(
|
||||
Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url,
|
||||
attempts: attempts
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -160,10 +160,13 @@ module QA
|
|||
# Get MR comments
|
||||
#
|
||||
# @return [Array]
|
||||
def comments(auto_paginate: false)
|
||||
def comments(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_comments_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url)
|
||||
auto_paginated_response(
|
||||
Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url,
|
||||
attempts: attempts
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -264,21 +264,24 @@ module QA
|
|||
|
||||
result = parse_body(response)
|
||||
|
||||
Runtime::Logger.error("Import failed: #{result[:import_error]}") if result[:import_status] == "failed"
|
||||
if result[:import_status] == "failed"
|
||||
Runtime::Logger.error("Import failed: #{result[:import_error]}")
|
||||
Runtime::Logger.error("Failed relations: #{result[:failed_relations]}")
|
||||
end
|
||||
|
||||
result[:import_status]
|
||||
end
|
||||
|
||||
def commits(auto_paginate: false)
|
||||
def commits(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_commits_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_commits_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_commits_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def merge_requests(auto_paginate: false)
|
||||
def merge_requests(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_merge_requests_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_merge_requests_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_merge_requests_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def merge_request_with_title(title)
|
||||
|
@ -302,10 +305,10 @@ module QA
|
|||
parse_body(response)
|
||||
end
|
||||
|
||||
def repository_branches(auto_paginate: false)
|
||||
def repository_branches(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_repository_branches_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_repository_branches_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_repository_branches_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def repository_tags
|
||||
|
@ -328,22 +331,22 @@ module QA
|
|||
parse_body(response)
|
||||
end
|
||||
|
||||
def issues(auto_paginate: false)
|
||||
def issues(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_issues_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_issues_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_issues_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def labels(auto_paginate: false)
|
||||
def labels(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_labels_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_labels_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_labels_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def milestones(auto_paginate: false)
|
||||
def milestones(auto_paginate: false, attempts: 0)
|
||||
return parse_body(api_get_from(api_milestones_path)) unless auto_paginate
|
||||
|
||||
auto_paginated_response(request_url(api_milestones_path, per_page: '100'))
|
||||
auto_paginated_response(request_url(api_milestones_path, per_page: '100'), attempts: attempts)
|
||||
end
|
||||
|
||||
def wikis
|
||||
|
|
|
@ -6,6 +6,10 @@ module QA
|
|||
class Request
|
||||
API_VERSION = 'v4'
|
||||
|
||||
def self.masked_url(url)
|
||||
url.sub(/private_token=.*/, "private_token=[****]")
|
||||
end
|
||||
|
||||
def initialize(api_client, path, **query_string)
|
||||
query_string[:private_token] ||= api_client.personal_access_token unless query_string[:oauth_access_token]
|
||||
request_path = request_path(path, **query_string)
|
||||
|
@ -13,7 +17,7 @@ module QA
|
|||
end
|
||||
|
||||
def mask_url
|
||||
@session_address.address.sub(/private_token=.*/, "private_token=[****]")
|
||||
QA::Runtime::API::Request.masked_url(url)
|
||||
end
|
||||
|
||||
def url
|
||||
|
|
|
@ -135,10 +135,12 @@ module QA
|
|||
imported_project # import the project
|
||||
fetch_github_objects # fetch all objects right after import has started
|
||||
|
||||
expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(
|
||||
duration: 3600,
|
||||
interval: 30
|
||||
)
|
||||
import_status = lambda do
|
||||
imported_project.reload!.import_status.tap do |status|
|
||||
raise "Import of '#{imported_project.name}' failed!" if status == 'failed'
|
||||
end
|
||||
end
|
||||
expect(import_status).to eventually_eq('finished').within(duration: 3600, interval: 30)
|
||||
@import_time = Time.now - start
|
||||
|
||||
aggregate_failures do
|
||||
|
@ -264,7 +266,7 @@ module QA
|
|||
def gl_commits
|
||||
@gl_commits ||= begin
|
||||
logger.debug("= Fetching commits =")
|
||||
imported_project.commits(auto_paginate: true).map { |c| c[:id] }
|
||||
imported_project.commits(auto_paginate: true, attempts: 2).map { |c| c[:id] }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -294,7 +296,7 @@ module QA
|
|||
def mrs
|
||||
@mrs ||= begin
|
||||
logger.debug("= Fetching merge requests =")
|
||||
imported_mrs = imported_project.merge_requests(auto_paginate: true)
|
||||
imported_mrs = imported_project.merge_requests(auto_paginate: true, attempts: 2)
|
||||
logger.debug("= Transforming merge request objects for comparison =")
|
||||
imported_mrs.each_with_object({}) do |mr, hash|
|
||||
resource = Resource::MergeRequest.init do |resource|
|
||||
|
@ -305,7 +307,7 @@ module QA
|
|||
|
||||
hash[mr[:title]] = {
|
||||
body: mr[:description],
|
||||
comments: resource.comments(auto_paginate: true)
|
||||
comments: resource.comments(auto_paginate: true, attempts: 2)
|
||||
# remove system notes
|
||||
.reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
|
||||
.map { |c| sanitize(c[:body]) }
|
||||
|
@ -320,7 +322,7 @@ module QA
|
|||
def gl_issues
|
||||
@gl_issues ||= begin
|
||||
logger.debug("= Fetching issues =")
|
||||
imported_issues = imported_project.issues(auto_paginate: true)
|
||||
imported_issues = imported_project.issues(auto_paginate: true, attempts: 2)
|
||||
logger.debug("= Transforming issue objects for comparison =")
|
||||
imported_issues.each_with_object({}) do |issue, hash|
|
||||
resource = Resource::Issue.init do |issue_resource|
|
||||
|
@ -331,7 +333,7 @@ module QA
|
|||
|
||||
hash[issue[:title]] = {
|
||||
body: issue[:description],
|
||||
comments: resource.comments(auto_paginate: true).map { |c| sanitize(c[:body]) }
|
||||
comments: resource.comments(auto_paginate: true, attempts: 2).map { |c| sanitize(c[:body]) }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,16 +79,27 @@ module QA
|
|||
error.response
|
||||
end
|
||||
|
||||
def auto_paginated_response(url)
|
||||
def auto_paginated_response(url, attempts: 0)
|
||||
pages = []
|
||||
with_paginated_response_body(url) { |response| pages << response }
|
||||
with_paginated_response_body(url, attempts: attempts) { |response| pages << response }
|
||||
|
||||
pages.flatten
|
||||
end
|
||||
|
||||
def with_paginated_response_body(url)
|
||||
def with_paginated_response_body(url, attempts: 0)
|
||||
not_ok_error = lambda do |resp|
|
||||
raise "Failed to GET #{QA::Runtime::API::Request.masked_url(url)} - (#{resp.code}): `#{resp}`."
|
||||
end
|
||||
|
||||
loop do
|
||||
response = get(url)
|
||||
response = if attempts > 0
|
||||
Retrier.retry_on_exception(max_attempts: attempts, log: false) do
|
||||
get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
|
||||
end
|
||||
else
|
||||
get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
|
||||
end
|
||||
|
||||
page, pages = response.headers.values_at(:x_page, :x_total_pages)
|
||||
api_endpoint = url.match(%r{v4/(\S+)\?})[1]
|
||||
|
||||
|
@ -104,7 +115,10 @@ module QA
|
|||
end
|
||||
|
||||
def pagination_links(response)
|
||||
response.headers[:link].split(',').map do |link|
|
||||
link = response.headers[:link]
|
||||
return unless link
|
||||
|
||||
link.split(',').map do |link|
|
||||
match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/)
|
||||
break nil unless match
|
||||
|
||||
|
|
|
@ -11,7 +11,15 @@ module QA
|
|||
RetriesExceededError = Class.new(RepeaterConditionExceededError)
|
||||
WaitExceededError = Class.new(RepeaterConditionExceededError)
|
||||
|
||||
def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
|
||||
def repeat_until(
|
||||
max_attempts: nil,
|
||||
max_duration: nil,
|
||||
reload_page: nil,
|
||||
sleep_interval: 0,
|
||||
raise_on_failure: true,
|
||||
retry_on_exception: false,
|
||||
log: true
|
||||
)
|
||||
attempts = 0
|
||||
start = Time.now
|
||||
|
||||
|
@ -29,17 +37,19 @@ module QA
|
|||
raise unless retry_on_exception
|
||||
|
||||
attempts += 1
|
||||
if remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
|
||||
sleep_and_reload_if_needed(sleep_interval, reload_page)
|
||||
raise unless remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
sleep_and_reload_if_needed(sleep_interval, reload_page)
|
||||
retry
|
||||
end
|
||||
|
||||
if raise_on_failure
|
||||
raise RetriesExceededError, "Retry condition not met after #{max_attempts} #{'attempt'.pluralize(max_attempts)}" unless remaining_attempts?(attempts, max_attempts)
|
||||
unless remaining_attempts?(attempts, max_attempts)
|
||||
raise(
|
||||
RetriesExceededError,
|
||||
"Retry condition not met after #{max_attempts} #{'attempt'.pluralize(max_attempts)}"
|
||||
)
|
||||
end
|
||||
|
||||
raise WaitExceededError, "Wait condition not met after #{max_duration} #{'second'.pluralize(max_duration)}"
|
||||
end
|
||||
|
|
|
@ -7,21 +7,21 @@ module QA
|
|||
|
||||
module_function
|
||||
|
||||
def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5)
|
||||
QA::Runtime::Logger.debug(
|
||||
<<~MSG.tr("\n", ' ')
|
||||
with retry_on_exception: max_attempts: #{max_attempts};
|
||||
reload_page: #{reload_page};
|
||||
sleep_interval: #{sleep_interval}
|
||||
MSG
|
||||
)
|
||||
def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5, log: true)
|
||||
if log
|
||||
msg = ["with retry_on_exception: max_attempts: #{max_attempts}"]
|
||||
msg << "reload_page: #{reload_page}" if reload_page
|
||||
msg << "sleep_interval: #{sleep_interval}"
|
||||
QA::Runtime::Logger.debug(msg.join('; '))
|
||||
end
|
||||
|
||||
result = nil
|
||||
repeat_until(
|
||||
max_attempts: max_attempts,
|
||||
reload_page: reload_page,
|
||||
sleep_interval: sleep_interval,
|
||||
retry_on_exception: true
|
||||
retry_on_exception: true,
|
||||
log: log
|
||||
) do
|
||||
result = yield
|
||||
|
||||
|
@ -29,7 +29,7 @@ module QA
|
|||
# We set it to `true` so that it doesn't repeat if there's no exception
|
||||
true
|
||||
end
|
||||
QA::Runtime::Logger.debug("ended retry_on_exception")
|
||||
QA::Runtime::Logger.debug("ended retry_on_exception") if log
|
||||
|
||||
result
|
||||
end
|
||||
|
|
|
@ -70,8 +70,9 @@ RSpec.describe QA::Support::Retrier do
|
|||
describe '.retry_on_exception' do
|
||||
context 'when the condition is true' do
|
||||
it 'logs max_attempts, reload_page, and sleep_interval parameters' do
|
||||
expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { true } }
|
||||
.to output(/with retry_on_exception: max_attempts: 1; reload_page: ; sleep_interval: 0/).to_stdout_from_any_process
|
||||
message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/
|
||||
expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { true } }
|
||||
.to output(message).to_stdout_from_any_process
|
||||
end
|
||||
|
||||
it 'logs the end' do
|
||||
|
@ -82,8 +83,9 @@ RSpec.describe QA::Support::Retrier do
|
|||
|
||||
context 'when the condition is false' do
|
||||
it 'logs the start' do
|
||||
expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { false } }
|
||||
.to output(/with retry_on_exception: max_attempts: 1; reload_page: ; sleep_interval: 0/).to_stdout_from_any_process
|
||||
message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/
|
||||
expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { false } }
|
||||
.to output(message).to_stdout_from_any_process
|
||||
end
|
||||
|
||||
it 'logs the end' do
|
||||
|
|
|
@ -652,7 +652,7 @@ RSpec.describe Projects::FeatureFlagsController do
|
|||
version: 'new_version_flag',
|
||||
strategies_attributes: [{
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '15', stickiness: 'DEFAULT' },
|
||||
parameters: { groupId: 'default', rollout: '15', stickiness: 'default' },
|
||||
scopes_attributes: [{ environment_scope: 'production' }]
|
||||
}]
|
||||
}
|
||||
|
@ -666,7 +666,7 @@ RSpec.describe Projects::FeatureFlagsController do
|
|||
|
||||
strategy_json = json_response['strategies'].first
|
||||
expect(strategy_json['name']).to eq('flexibleRollout')
|
||||
expect(strategy_json['parameters']).to eq({ 'groupId' => 'default', 'rollout' => '15', 'stickiness' => 'DEFAULT' })
|
||||
expect(strategy_json['parameters']).to eq({ 'groupId' => 'default', 'rollout' => '15', 'stickiness' => 'default' })
|
||||
expect(strategy_json['scopes'].count).to eq(1)
|
||||
|
||||
scope_json = strategy_json['scopes'].first
|
||||
|
@ -938,7 +938,7 @@ RSpec.describe Projects::FeatureFlagsController do
|
|||
it 'creates a flexibleRollout strategy' do
|
||||
put_request(new_version_flag, strategies_attributes: [{
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '30', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '30', stickiness: 'default' }
|
||||
}])
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -948,7 +948,7 @@ RSpec.describe Projects::FeatureFlagsController do
|
|||
expect(strategy_json['parameters']).to eq({
|
||||
'groupId' => 'default',
|
||||
'rollout' => '30',
|
||||
'stickiness' => 'DEFAULT'
|
||||
'stickiness' => 'default'
|
||||
})
|
||||
expect(strategy_json['scopes']).to eq([])
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ FactoryBot.define do
|
|||
ci_job_token_scope_enabled { nil }
|
||||
end
|
||||
|
||||
before(:create) do |project, evaluator|
|
||||
after(:build) do |project, evaluator|
|
||||
# Builds and MRs can't have higher visibility level than repository access level.
|
||||
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
|
||||
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
|
||||
|
@ -67,21 +67,11 @@ FactoryBot.define do
|
|||
pages_access_level: evaluator.pages_access_level,
|
||||
metrics_dashboard_access_level: evaluator.metrics_dashboard_access_level,
|
||||
operations_access_level: evaluator.operations_access_level,
|
||||
analytics_access_level: evaluator.analytics_access_level
|
||||
analytics_access_level: evaluator.analytics_access_level,
|
||||
container_registry_access_level: evaluator.container_registry_access_level
|
||||
}
|
||||
|
||||
project.build_project_feature(hash)
|
||||
|
||||
# This is not included in the `hash` above because the default_value_for in
|
||||
# the ProjectFeature model overrides the value set by `build_project_feature` when
|
||||
# evaluator.container_registry_access_level == ProjectFeature::DISABLED.
|
||||
#
|
||||
# This is because the default_value_for gem uses the <column>_changed? method
|
||||
# to determine if the default value should be applied. For new records,
|
||||
# <column>_changed? returns false if the value of the column is the same as
|
||||
# the database default.
|
||||
# See https://github.com/FooBarWidget/default_value_for/blob/release-3.4.0/lib/default_value_for.rb#L158.
|
||||
project.project_feature.container_registry_access_level = evaluator.container_registry_access_level
|
||||
end
|
||||
|
||||
after(:create) do |project, evaluator|
|
||||
|
|
|
@ -66,15 +66,14 @@ describe('feature_flags/components/strategies/flexible_rollout.vue', () => {
|
|||
});
|
||||
|
||||
it('emits a change when the stickiness value changes', async () => {
|
||||
stickinessSelect.setValue('USERID');
|
||||
await wrapper.vm.$nextTick();
|
||||
await stickinessSelect.setValue('userId');
|
||||
expect(wrapper.emitted('change')).toEqual([
|
||||
[
|
||||
{
|
||||
parameters: {
|
||||
rollout: flexibleRolloutStrategy.parameters.rollout,
|
||||
groupId: PERCENT_ROLLOUT_GROUP_ID,
|
||||
stickiness: 'USERID',
|
||||
stickiness: 'userId',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -76,7 +76,7 @@ export const percentRolloutStrategy = {
|
|||
|
||||
export const flexibleRolloutStrategy = {
|
||||
name: ROLLOUT_STRATEGY_FLEXIBLE_ROLLOUT,
|
||||
parameters: { rollout: '50', groupId: 'default', stickiness: 'DEFAULT' },
|
||||
parameters: { rollout: '50', groupId: 'default', stickiness: 'default' },
|
||||
scopes: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('operations_feature_flags_correct_flexible_rollout_values')
|
||||
|
||||
RSpec.describe OperationsFeatureFlagsCorrectFlexibleRolloutValues, :migration do
|
||||
let_it_be(:strategies) { table(:operations_strategies) }
|
||||
|
||||
let(:namespace) { table(:namespaces).create!(name: 'feature_flag', path: 'feature_flag') }
|
||||
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
|
||||
let(:feature_flag) { table(:operations_feature_flags).create!(project_id: project.id, active: true, name: 'foo', iid: 1) }
|
||||
|
||||
describe "#up" do
|
||||
described_class::STICKINESS.each do |old, new|
|
||||
it "corrects parameters for flexible rollout stickiness #{old}" do
|
||||
reversible_migration do |migration|
|
||||
parameters = { groupId: "default", rollout: "100", stickiness: old }
|
||||
strategy = create_strategy(parameters)
|
||||
|
||||
migration.before -> {
|
||||
expect(strategy.reload.parameters).to eq({ "groupId" => "default", "rollout" => "100", "stickiness" => old })
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(strategy.reload.parameters).to eq({ "groupId" => "default", "rollout" => "100", "stickiness" => new })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores other strategies' do
|
||||
reversible_migration do |migration|
|
||||
parameters = { "groupId" => "default", "rollout" => "100", "stickiness" => "USERID" }
|
||||
strategy = create_strategy(parameters, name: 'default')
|
||||
|
||||
migration.before -> {
|
||||
expect(strategy.reload.parameters).to eq(parameters)
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(strategy.reload.parameters).to eq(parameters)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores other stickiness' do
|
||||
reversible_migration do |migration|
|
||||
parameters = { "groupId" => "default", "rollout" => "100", "stickiness" => "FOO" }
|
||||
strategy = create_strategy(parameters)
|
||||
|
||||
migration.before -> {
|
||||
expect(strategy.reload.parameters).to eq(parameters)
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(strategy.reload.parameters).to eq(parameters)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_strategy(params, name: 'flexibleRollout')
|
||||
strategies.create!(name: name, parameters: params, feature_flag_id: feature_flag.id)
|
||||
end
|
||||
end
|
|
@ -208,23 +208,9 @@ RSpec.describe Namespace do
|
|||
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
|
||||
it { is_expected.to include_module(Namespaces::Traversal::Recursive) }
|
||||
it { is_expected.to include_module(Namespaces::Traversal::Linear) }
|
||||
it { is_expected.to include_module(Namespaces::Traversal::RecursiveScopes) }
|
||||
it { is_expected.to include_module(Namespaces::Traversal::LinearScopes) }
|
||||
end
|
||||
|
||||
context 'traversal scopes' do
|
||||
context 'recursive' do
|
||||
before do
|
||||
stub_feature_flags(use_traversal_ids: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'namespace traversal scopes'
|
||||
end
|
||||
|
||||
context 'linear' do
|
||||
it_behaves_like 'namespace traversal scopes'
|
||||
end
|
||||
end
|
||||
it_behaves_like 'linear namespace traversal'
|
||||
|
||||
context 'traversal_ids on create' do
|
||||
context 'default traversal_ids' do
|
||||
|
|
|
@ -112,7 +112,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
end
|
||||
|
||||
context 'when the strategy name is flexibleRollout' do
|
||||
valid_parameters = { rollout: '40', groupId: 'mygroup', stickiness: 'DEFAULT' }
|
||||
valid_parameters = { rollout: '40', groupId: 'mygroup', stickiness: 'default' }
|
||||
where(invalid_parameters: [
|
||||
nil,
|
||||
{},
|
||||
|
@ -133,7 +133,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
|
||||
[
|
||||
[:rollout, '10'],
|
||||
[:stickiness, 'DEFAULT'],
|
||||
[:stickiness, 'default'],
|
||||
[:groupId, 'mygroup']
|
||||
].permutation(3).each do |parameters|
|
||||
it "allows the parameters in the order #{parameters.map { |p| p.first }.join(', ')}" do
|
||||
|
@ -151,7 +151,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
"\n", "\t", "\n10", "20\n", "\n100", "100\n", "\n ", nil])
|
||||
with_them do
|
||||
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
|
||||
parameters = { stickiness: 'DEFAULT', groupId: 'mygroup', rollout: invalid_value }
|
||||
parameters = { stickiness: 'default', groupId: 'mygroup', rollout: invalid_value }
|
||||
strategy = described_class.create(feature_flag: feature_flag,
|
||||
name: 'flexibleRollout',
|
||||
parameters: parameters)
|
||||
|
@ -165,7 +165,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
where(valid_value: %w[0 1 10 38 100 93])
|
||||
with_them do
|
||||
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
|
||||
parameters = { stickiness: 'DEFAULT', groupId: 'mygroup', rollout: valid_value }
|
||||
parameters = { stickiness: 'default', groupId: 'mygroup', rollout: valid_value }
|
||||
strategy = described_class.create(feature_flag: feature_flag,
|
||||
name: 'flexibleRollout',
|
||||
parameters: parameters)
|
||||
|
@ -180,7 +180,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
'!bad', '.bad', 'Bad', 'bad1', "", " ", "b" * 33, "ba_d", "ba\nd"])
|
||||
with_them do
|
||||
it 'must be a string value of up to 32 lowercase characters' do
|
||||
parameters = { stickiness: 'DEFAULT', groupId: invalid_value, rollout: '40' }
|
||||
parameters = { stickiness: 'default', groupId: invalid_value, rollout: '40' }
|
||||
strategy = described_class.create(feature_flag: feature_flag,
|
||||
name: 'flexibleRollout',
|
||||
parameters: parameters)
|
||||
|
@ -192,7 +192,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
where(valid_value: ["somegroup", "anothergroup", "okay", "g", "a" * 32])
|
||||
with_them do
|
||||
it 'must be a string value of up to 32 lowercase characters' do
|
||||
parameters = { stickiness: 'DEFAULT', groupId: valid_value, rollout: '40' }
|
||||
parameters = { stickiness: 'default', groupId: valid_value, rollout: '40' }
|
||||
strategy = described_class.create(feature_flag: feature_flag,
|
||||
name: 'flexibleRollout',
|
||||
parameters: parameters)
|
||||
|
@ -203,7 +203,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
end
|
||||
|
||||
describe 'stickiness' do
|
||||
where(invalid_value: [nil, " ", "default", "DEFAULT\n", "UserId", "USER", "USERID "])
|
||||
where(invalid_value: [nil, " ", "DEFAULT", "DEFAULT\n", "UserId", "USER", "USERID "])
|
||||
with_them do
|
||||
it 'must be a string representing a supported stickiness setting' do
|
||||
parameters = { stickiness: invalid_value, groupId: 'mygroup', rollout: '40' }
|
||||
|
@ -212,12 +212,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
parameters: parameters)
|
||||
|
||||
expect(strategy.errors[:parameters]).to eq([
|
||||
'stickiness parameter must be DEFAULT, USERID, SESSIONID, or RANDOM'
|
||||
'stickiness parameter must be default, userId, sessionId, or random'
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
where(valid_value: %w[DEFAULT USERID SESSIONID RANDOM])
|
||||
where(valid_value: %w[default userId sessionId random])
|
||||
with_them do
|
||||
it 'must be a string representing a supported stickiness setting' do
|
||||
parameters = { stickiness: valid_value, groupId: 'mygroup', rollout: '40' }
|
||||
|
@ -425,7 +425,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
user_list: user_list,
|
||||
parameters: { groupId: 'default',
|
||||
rollout: '10',
|
||||
stickiness: 'DEFAULT' })
|
||||
stickiness: 'default' })
|
||||
|
||||
expect(strategy.errors[:user_list]).to eq(['must be blank'])
|
||||
end
|
||||
|
@ -435,7 +435,7 @@ RSpec.describe Operations::FeatureFlags::Strategy do
|
|||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default',
|
||||
rollout: '10',
|
||||
stickiness: 'DEFAULT' })
|
||||
stickiness: 'default' })
|
||||
|
||||
expect(strategy.errors[:user_list]).to be_empty
|
||||
end
|
||||
|
|
|
@ -218,5 +218,29 @@ RSpec.describe ProjectFeature do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'test build factory' do
|
||||
let(:project) { build(:project, container_registry_access_level: level) }
|
||||
|
||||
subject { project.container_registry_access_level }
|
||||
|
||||
context 'private' do
|
||||
let(:level) { ProjectFeature::PRIVATE }
|
||||
|
||||
it { is_expected.to eq(level) }
|
||||
end
|
||||
|
||||
context 'enabled' do
|
||||
let(:level) { ProjectFeature::ENABLED }
|
||||
|
||||
it { is_expected.to eq(level) }
|
||||
end
|
||||
|
||||
context 'disabled' do
|
||||
let(:level) { ProjectFeature::DISABLED }
|
||||
|
||||
it { is_expected.to eq(level) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -318,7 +318,8 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
|
||||
it 'validates presence of project_feature' do
|
||||
project = build(:project, project_feature: nil)
|
||||
project = build(:project)
|
||||
project.project_feature = nil
|
||||
|
||||
expect(project).not_to be_valid
|
||||
end
|
||||
|
|
|
@ -417,7 +417,7 @@ RSpec.describe API::FeatureFlags do
|
|||
version: 'new_version_flag',
|
||||
strategies: [{
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '50', stickiness: 'DEFAULT' },
|
||||
parameters: { groupId: 'default', rollout: '50', stickiness: 'default' },
|
||||
scopes: [{
|
||||
environment_scope: 'staging'
|
||||
}]
|
||||
|
@ -434,7 +434,7 @@ RSpec.describe API::FeatureFlags do
|
|||
expect(feature_flag.version).to eq('new_version_flag')
|
||||
expect(feature_flag.strategies.map { |s| s.slice(:name, :parameters).deep_symbolize_keys }).to eq([{
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '50', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '50', stickiness: 'default' }
|
||||
}])
|
||||
expect(feature_flag.strategies.first.scopes.map { |s| s.slice(:environment_scope).deep_symbolize_keys }).to eq([{
|
||||
environment_scope: 'staging'
|
||||
|
@ -630,7 +630,7 @@ RSpec.describe API::FeatureFlags do
|
|||
strategies: [{
|
||||
id: strategy.id,
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'default' }
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -642,7 +642,7 @@ RSpec.describe API::FeatureFlags do
|
|||
expect(result).to eq([{
|
||||
id: strategy.id,
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'default' }
|
||||
}])
|
||||
end
|
||||
|
||||
|
@ -677,7 +677,7 @@ RSpec.describe API::FeatureFlags do
|
|||
params = {
|
||||
strategies: [{
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'default' }
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -694,7 +694,7 @@ RSpec.describe API::FeatureFlags do
|
|||
parameters: {}
|
||||
}, {
|
||||
name: 'flexibleRollout',
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' }
|
||||
parameters: { groupId: 'default', rollout: '10', stickiness: 'default' }
|
||||
}])
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Traversal examples common to linear and recursive methods are in
|
||||
# spec/support/shared_examples/namespaces/traversal_examples.rb
|
||||
|
||||
RSpec.shared_examples 'linear namespace traversal' do
|
||||
context 'when use_traversal_ids feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(use_traversal_ids: true)
|
||||
end
|
||||
|
||||
context 'scopes' do
|
||||
describe '.as_ids' do
|
||||
let_it_be(:namespace1) { create(:group) }
|
||||
let_it_be(:namespace2) { create(:group) }
|
||||
|
||||
subject { Namespace.where(id: [namespace1, namespace2]).as_ids.pluck(:id) }
|
||||
|
||||
it { is_expected.to contain_exactly(namespace1.id, namespace2.id) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,51 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'namespace traversal scopes' do
|
||||
# Hierarchy 1
|
||||
let_it_be(:group_1) { create(:group) }
|
||||
let_it_be(:nested_group_1) { create(:group, parent: group_1) }
|
||||
let_it_be(:deep_nested_group_1) { create(:group, parent: nested_group_1) }
|
||||
|
||||
# Hierarchy 2
|
||||
let_it_be(:group_2) { create(:group) }
|
||||
let_it_be(:nested_group_2) { create(:group, parent: group_2) }
|
||||
let_it_be(:deep_nested_group_2) { create(:group, parent: nested_group_2) }
|
||||
|
||||
# All groups
|
||||
let_it_be(:groups) do
|
||||
[
|
||||
group_1, nested_group_1, deep_nested_group_1,
|
||||
group_2, nested_group_2, deep_nested_group_2
|
||||
]
|
||||
end
|
||||
|
||||
describe '.as_ids' do
|
||||
subject { described_class.where(id: [group_1, group_2]).as_ids.pluck(:id) }
|
||||
|
||||
it { is_expected.to contain_exactly(group_1.id, group_2.id) }
|
||||
end
|
||||
|
||||
describe '.without_sti_condition' do
|
||||
subject { described_class.without_sti_condition }
|
||||
|
||||
it { expect(subject.where_values_hash).not_to have_key(:type) }
|
||||
end
|
||||
|
||||
describe '.self_and_descendants' do
|
||||
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants }
|
||||
|
||||
it { is_expected.to contain_exactly(nested_group_1, deep_nested_group_1, nested_group_2, deep_nested_group_2) }
|
||||
|
||||
context 'with duplicate descendants' do
|
||||
subject { described_class.where(id: [group_1, group_2, nested_group_1]).self_and_descendants }
|
||||
|
||||
it { is_expected.to match_array(groups) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.self_and_descendant_ids' do
|
||||
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendant_ids.pluck(:id) }
|
||||
|
||||
it { is_expected.to contain_exactly(nested_group_1.id, deep_nested_group_1.id, nested_group_2.id, deep_nested_group_2.id) }
|
||||
end
|
||||
end
|
24
yarn.lock
24
yarn.lock
|
@ -3418,12 +3418,12 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
|
||||
commander@2, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@~2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^6.0.0, commander@^6.2.0, commander@~6.2.1:
|
||||
commander@^6.0.0, commander@^6.1.0, commander@^6.2.0, commander@~6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
@ -10560,7 +10560,7 @@ semver-diff@^3.1.1:
|
|||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
@ -12882,14 +12882,14 @@ yarn-check-webpack-plugin@^1.2.0:
|
|||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
|
||||
yarn-deduplicate@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d"
|
||||
integrity sha512-2FDJ1dFmtvqhRmfja89ohYzpaheCYg7BFBSyaUq+kxK0y61C9oHv1XaQovCWGJtP2WU8PksQOgzMVV7oQOobzw==
|
||||
yarn-deduplicate@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-3.1.0.tgz#3018d93e95f855f236a215b591fe8bc4bcabba3e"
|
||||
integrity sha512-q2VZ6ThNzQpGfNpkPrkmV7x5HT9MOhCUsTxVTzyyZB0eSXz1NTodHn+r29DlLb+peKk8iXxzdUVhQG9pI7moFw==
|
||||
dependencies:
|
||||
"@yarnpkg/lockfile" "^1.1.0"
|
||||
commander "^2.10.0"
|
||||
semver "^5.3.0"
|
||||
commander "^6.1.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
|
|
Loading…
Reference in New Issue