2022-11-25 23:54:43 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Admin
|
|
|
|
class SetFeatureFlagService
|
2023-03-04 22:38:38 +05:30
|
|
|
UnknownOperationError = Class.new(StandardError)
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
def initialize(feature_flag_name:, params:)
|
|
|
|
@name = feature_flag_name
|
2023-03-04 22:38:38 +05:30
|
|
|
@target = Feature::Target.new(params)
|
2022-11-25 23:54:43 +05:30
|
|
|
@params = params
|
2023-03-04 22:38:38 +05:30
|
|
|
@force = params[:force]
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
2023-03-04 22:38:38 +05:30
|
|
|
unless force
|
2022-11-25 23:54:43 +05:30
|
|
|
error = validate_feature_flag_name
|
|
|
|
return ServiceResponse.error(message: error, reason: :invalid_feature_flag) if error
|
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
if target.gate_specified?
|
|
|
|
update_targets
|
2022-11-25 23:54:43 +05:30
|
|
|
else
|
2023-03-04 22:38:38 +05:30
|
|
|
update_global
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
feature_flag = Feature.get(name) # rubocop:disable Gitlab/AvoidFeatureGet
|
|
|
|
|
|
|
|
ServiceResponse.success(payload: { feature_flag: feature_flag })
|
2023-03-04 22:38:38 +05:30
|
|
|
rescue Feature::InvalidOperation => e
|
|
|
|
ServiceResponse.error(message: e.message, reason: :illegal_operation)
|
|
|
|
rescue UnknownOperationError => e
|
|
|
|
ServiceResponse.error(message: e.message, reason: :illegal_operation)
|
|
|
|
rescue Feature::Target::UnknownTargetError => e
|
2022-11-25 23:54:43 +05:30
|
|
|
ServiceResponse.error(message: e.message, reason: :actor_not_found)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
attr_reader :name, :params, :target, :force
|
2022-11-25 23:54:43 +05:30
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
# Note: the if expressions in `update_targets` and `update_global` are order dependant.
|
|
|
|
def update_targets
|
|
|
|
target.targets.each do |target|
|
|
|
|
if enable?
|
|
|
|
enable(target)
|
|
|
|
elsif disable?
|
|
|
|
Feature.disable(name, target)
|
|
|
|
elsif opt_out?
|
|
|
|
Feature.opt_out(name, target)
|
|
|
|
elsif remove_opt_out?
|
|
|
|
remove_opt_out(target)
|
|
|
|
else
|
|
|
|
raise UnknownOperationError, "Cannot set '#{name}' to #{value.inspect} for #{target}"
|
|
|
|
end
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def update_global
|
|
|
|
if enable?
|
|
|
|
Feature.enable(name)
|
|
|
|
elsif disable?
|
2022-11-25 23:54:43 +05:30
|
|
|
Feature.disable(name)
|
2023-03-04 22:38:38 +05:30
|
|
|
elsif percentage_of_actors?
|
|
|
|
Feature.enable_percentage_of_actors(name, percentage)
|
|
|
|
elsif percentage_of_time?
|
|
|
|
Feature.enable_percentage_of_time(name, percentage)
|
|
|
|
else
|
|
|
|
msg = if key.present?
|
|
|
|
"Cannot set '#{name}' (#{key.inspect}) to #{value.inspect}"
|
|
|
|
else
|
|
|
|
"Cannot set '#{name}' to #{value.inspect}"
|
|
|
|
end
|
|
|
|
|
|
|
|
raise UnknownOperationError, msg
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def remove_opt_out(target)
|
|
|
|
raise Feature::InvalidOperation, "No opt-out exists for #{target}" unless Feature.opted_out?(name, target)
|
|
|
|
|
|
|
|
Feature.remove_opt_out(name, target)
|
|
|
|
end
|
|
|
|
|
|
|
|
def enable(target)
|
|
|
|
if Feature.opted_out?(name, target)
|
|
|
|
target_name = target.respond_to?(:to_reference) ? target.to_reference : target.to_s
|
|
|
|
raise Feature::InvalidOperation, "Opt-out exists for #{target_name} - remove opt-out before enabling"
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
2023-03-04 22:38:38 +05:30
|
|
|
|
|
|
|
Feature.enable(name, target)
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def value
|
|
|
|
params[:value]
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def key
|
|
|
|
params[:key]
|
|
|
|
end
|
|
|
|
|
|
|
|
def numeric_value?
|
|
|
|
params[:value].match?(/^\d+(\.\d+)?$/)
|
|
|
|
end
|
|
|
|
|
|
|
|
def percentage
|
|
|
|
raise UnknownOperationError, "Not a percentage" unless numeric_value?
|
|
|
|
|
|
|
|
value.to_f
|
|
|
|
end
|
|
|
|
|
|
|
|
def percentage_of_actors?
|
|
|
|
key == 'percentage_of_actors'
|
|
|
|
end
|
|
|
|
|
|
|
|
def percentage_of_time?
|
|
|
|
return true if key == 'percentage_of_time'
|
|
|
|
return numeric_value? if key.nil?
|
|
|
|
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
# Note: `key` is NOT considered - setting to a percentage to 0 is the same as disabling.
|
|
|
|
def disable?
|
|
|
|
value.in?(%w[0 0.0 false])
|
|
|
|
end
|
|
|
|
|
|
|
|
# Note: `key` is NOT considered - setting to a percentage to 100 is the same
|
|
|
|
def enable?
|
|
|
|
value.in?(%w[100 100.0 true])
|
|
|
|
end
|
|
|
|
|
|
|
|
def opt_out?
|
|
|
|
value == 'opt_out'
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_opt_out?
|
|
|
|
value == 'remove_opt_out'
|
|
|
|
end
|
|
|
|
|
|
|
|
def validate_feature_flag_name
|
|
|
|
## Overridden in EE
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Admin::SetFeatureFlagService.prepend_mod
|