rox-rollout 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +45 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/LICENSE +50 -0
- data/README_DEVELOP.md +19 -0
- data/Rakefile +16 -0
- data/_archive/.document +5 -0
- data/_archive/.rspec +1 -0
- data/_archive/Gemfile +15 -0
- data/_archive/Gemfile.lock +87 -0
- data/_archive/README.md +32 -0
- data/_archive/README.rdoc +19 -0
- data/_archive/Rakefile +50 -0
- data/_archive/lib/expr_function_definition.rb +52 -0
- data/_archive/lib/function_definition.rb +48 -0
- data/_archive/lib/function_token.rb +12 -0
- data/_archive/lib/object_extends.rb +12 -0
- data/_archive/lib/ruby_interpreter.rb +292 -0
- data/_archive/lib/stack.rb +48 -0
- data/_archive/lib/string_extends.rb +14 -0
- data/_archive/spec/ruby_interpreter_spec.rb +203 -0
- data/_archive/spec/spec_helper.rb +30 -0
- data/_archive/spec/stack_spec.rb +77 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/e2e/container.rb +38 -0
- data/e2e/custom_props.rb +55 -0
- data/e2e/rox_e2e_test.rb +159 -0
- data/e2e/test_vars.rb +24 -0
- data/lib/rox.rb +5 -0
- data/lib/rox/core/client/buid.rb +82 -0
- data/lib/rox/core/client/device_properties.rb +45 -0
- data/lib/rox/core/client/internal_flags.rb +20 -0
- data/lib/rox/core/client/sdk_settings.rb +5 -0
- data/lib/rox/core/configuration/configuration.rb +5 -0
- data/lib/rox/core/configuration/configuration_fetched_args.rb +23 -0
- data/lib/rox/core/configuration/configuration_fetched_invoker.rb +37 -0
- data/lib/rox/core/configuration/configuration_parser.rb +85 -0
- data/lib/rox/core/configuration/fetcher_error.rb +13 -0
- data/lib/rox/core/configuration/fetcher_status.rb +10 -0
- data/lib/rox/core/configuration/models/experiment_model.rb +5 -0
- data/lib/rox/core/configuration/models/target_group_model.rb +5 -0
- data/lib/rox/core/consts/build.rb +8 -0
- data/lib/rox/core/consts/environment.rb +42 -0
- data/lib/rox/core/consts/property_type.rb +29 -0
- data/lib/rox/core/context/merged_context.rb +16 -0
- data/lib/rox/core/core.rb +131 -0
- data/lib/rox/core/entities/flag.rb +26 -0
- data/lib/rox/core/entities/flag_setter.rb +39 -0
- data/lib/rox/core/entities/variant.rb +56 -0
- data/lib/rox/core/impression/impression_args.rb +5 -0
- data/lib/rox/core/impression/impression_invoker.rb +41 -0
- data/lib/rox/core/impression/models/experiment.rb +14 -0
- data/lib/rox/core/impression/models/reporting_value.rb +5 -0
- data/lib/rox/core/logging/logging.rb +17 -0
- data/lib/rox/core/logging/no_op_logger.rb +11 -0
- data/lib/rox/core/network/configuration_fetch_result.rb +5 -0
- data/lib/rox/core/network/configuration_fetcher.rb +38 -0
- data/lib/rox/core/network/configuration_fetcher_base.rb +25 -0
- data/lib/rox/core/network/configuration_fetcher_roxy.rb +29 -0
- data/lib/rox/core/network/configuration_source.rb +9 -0
- data/lib/rox/core/network/request.rb +46 -0
- data/lib/rox/core/network/request_configuration_builder.rb +48 -0
- data/lib/rox/core/network/request_data.rb +5 -0
- data/lib/rox/core/network/response.rb +16 -0
- data/lib/rox/core/properties/custom_property.rb +18 -0
- data/lib/rox/core/properties/custom_property_type.rb +18 -0
- data/lib/rox/core/properties/device_property.rb +11 -0
- data/lib/rox/core/register/registerer.rb +35 -0
- data/lib/rox/core/reporting/error_reporter.rb +152 -0
- data/lib/rox/core/repositories/custom_property_repository.rb +61 -0
- data/lib/rox/core/repositories/experiment_repository.rb +21 -0
- data/lib/rox/core/repositories/flag_repository.rb +49 -0
- data/lib/rox/core/repositories/roxx/experiments_extensions.rb +82 -0
- data/lib/rox/core/repositories/roxx/properties_extensions.rb +26 -0
- data/lib/rox/core/repositories/target_group_repository.rb +17 -0
- data/lib/rox/core/roxx/core_stack.rb +22 -0
- data/lib/rox/core/roxx/evaluation_result.rb +28 -0
- data/lib/rox/core/roxx/node.rb +11 -0
- data/lib/rox/core/roxx/parser.rb +143 -0
- data/lib/rox/core/roxx/regular_expression_extensions.rb +33 -0
- data/lib/rox/core/roxx/string_tokenizer.rb +68 -0
- data/lib/rox/core/roxx/symbols.rb +14 -0
- data/lib/rox/core/roxx/token_type.rb +30 -0
- data/lib/rox/core/roxx/tokenized_expression.rb +119 -0
- data/lib/rox/core/roxx/value_compare_extensions.rb +137 -0
- data/lib/rox/core/security/signature_verifier.rb +18 -0
- data/lib/rox/core/utils/periodic_task.rb +12 -0
- data/lib/rox/core/utils/type_utils.rb +9 -0
- data/lib/rox/server/client/sdk_settings.rb +5 -0
- data/lib/rox/server/client/server_properties.rb +20 -0
- data/lib/rox/server/flags/rox_flag.rb +19 -0
- data/lib/rox/server/flags/rox_variant.rb +8 -0
- data/lib/rox/server/logging/server_logger.rb +35 -0
- data/lib/rox/server/rox_options.rb +27 -0
- data/lib/rox/server/rox_server.rb +83 -0
- data/lib/rox/version.rb +3 -0
- data/rox.gemspec +29 -0
- metadata +184 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class Response
|
|
4
|
+
attr_accessor :status_code, :text
|
|
5
|
+
|
|
6
|
+
def initialize(status_code, text)
|
|
7
|
+
@status_code = status_code
|
|
8
|
+
@text = text
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def success_status_code?
|
|
12
|
+
@status_code >= 200 && @status_code < 300
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class CustomProperty
|
|
4
|
+
attr_accessor :name, :type
|
|
5
|
+
|
|
6
|
+
def initialize(name, type, value = nil, &block)
|
|
7
|
+
@name = name
|
|
8
|
+
@type = type
|
|
9
|
+
@value = value
|
|
10
|
+
@block = block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def value(context)
|
|
14
|
+
@block.nil? ? @value : @block.call(context)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class CustomPropertyType
|
|
4
|
+
attr_accessor :type, :external_type
|
|
5
|
+
|
|
6
|
+
def initialize(type, external_type)
|
|
7
|
+
@type = type
|
|
8
|
+
@external_type = external_type
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
STRING = CustomPropertyType.new('string', 'String')
|
|
12
|
+
BOOL = CustomPropertyType.new('bool', 'Boolean')
|
|
13
|
+
INT = CustomPropertyType.new('int', 'Number')
|
|
14
|
+
FLOAT = CustomPropertyType.new('double', 'Number')
|
|
15
|
+
SEMVER = CustomPropertyType.new('semver', 'Semver')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class Registerer
|
|
4
|
+
def initialize(flag_repository)
|
|
5
|
+
@flag_repository = flag_repository
|
|
6
|
+
@namespaces = []
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def register_instance(container, ns)
|
|
11
|
+
raise ArgumentError, 'A namespace cannot be null' if ns.nil?
|
|
12
|
+
|
|
13
|
+
@mutex.synchronize do
|
|
14
|
+
raise ArgumentError, "A container with the given namespace (#{ns}) has already been registered" if @namespaces.include?(ns)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@namespaces << ns
|
|
18
|
+
|
|
19
|
+
container.methods.each do |method_name|
|
|
20
|
+
method = container.method(method_name)
|
|
21
|
+
next unless method.arity.zero?
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
value = method.call
|
|
25
|
+
if value.is_a?(Variant)
|
|
26
|
+
@flag_repository.add_flag(value, ns == '' ? method_name.to_s : "#{ns}.#{method_name}")
|
|
27
|
+
end
|
|
28
|
+
rescue StandardError
|
|
29
|
+
next
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'rox/core/logging/logging'
|
|
2
|
+
|
|
3
|
+
module Rox
|
|
4
|
+
module Core
|
|
5
|
+
class ErrorReporter
|
|
6
|
+
BUGSNAG_NOTIFY_URL = 'https://notify.bugsnag.com'.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(request, device_properties, buid)
|
|
9
|
+
@request = request
|
|
10
|
+
@device_properties = device_properties
|
|
11
|
+
@buid = buid
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def report(message, ex)
|
|
15
|
+
return if @device_properties.rollout_environment == 'LOCAL'
|
|
16
|
+
|
|
17
|
+
Logging.logger.error("Error report: #{message}", ex)
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
stack_trace = ex.nil? ? caller : ex.backtrace
|
|
21
|
+
stack = parse_stack_trace(stack_trace)
|
|
22
|
+
payload = create_payload(message, ex, stack)
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
Logging.logger.error('failed to create bugsnag json payload of the error', e)
|
|
25
|
+
else
|
|
26
|
+
Thread.new { send_error(payload) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def send_error(payload)
|
|
31
|
+
Logging.logger.debug('Sending bugsnag error report...')
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
@request.send_post(ErrorReporter::BUGSNAG_NOTIFY_URL, payload)
|
|
35
|
+
Logging.logger.debug('Bugsnag error report was sent')
|
|
36
|
+
rescue StandardError => ex
|
|
37
|
+
Logging.logger.error('Failed to send bugsnag error ', ex)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_payload(message, ex, stack)
|
|
42
|
+
payload = {}
|
|
43
|
+
add_api_key(payload)
|
|
44
|
+
add_notifier(payload)
|
|
45
|
+
add_events(message, ex, stack, payload)
|
|
46
|
+
|
|
47
|
+
payload
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_metadata(message, ev)
|
|
51
|
+
inner_data = {
|
|
52
|
+
'message' => message,
|
|
53
|
+
'deviceId' => @device_properties.distinct_id,
|
|
54
|
+
'buid' => @buid.to_s
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
metadata = {
|
|
58
|
+
'data' => inner_data
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
ev['metaData'] = metadata
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_api_key(payload)
|
|
65
|
+
payload['apiKey'] = '9569ec14f61546c6aa2a97856492bf4d'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_events(message, ex, stack, payload)
|
|
69
|
+
evs = []
|
|
70
|
+
add_event(message, ex, stack, evs)
|
|
71
|
+
payload['events'] = evs
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def add_event(message, ex, stack, events)
|
|
75
|
+
ev = {}
|
|
76
|
+
add_payload_version(ev)
|
|
77
|
+
add_exceptions(message, ex, stack, ev)
|
|
78
|
+
add_user('id', @device_properties.rollout_key, ev)
|
|
79
|
+
add_metadata(message, ev)
|
|
80
|
+
add_app(ev)
|
|
81
|
+
|
|
82
|
+
events << ev
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def add_payload_version(ev)
|
|
86
|
+
ev['payloadVersion'] = 2
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_notifier(payload)
|
|
90
|
+
notifier = {
|
|
91
|
+
'name' => 'Rollout Ruby SDK',
|
|
92
|
+
'version' => @device_properties.lib_version
|
|
93
|
+
}
|
|
94
|
+
payload['notifier'] = notifier
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_user(id, rollout_key, ev)
|
|
98
|
+
user = {
|
|
99
|
+
id => rollout_key
|
|
100
|
+
}
|
|
101
|
+
ev['user'] = user
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def add_exceptions(message, ex, stack, ev)
|
|
105
|
+
exceptions = []
|
|
106
|
+
exception = {}
|
|
107
|
+
|
|
108
|
+
if ex.nil?
|
|
109
|
+
exception['errorClass'] = message
|
|
110
|
+
exception['message'] = message
|
|
111
|
+
exception['stacktrace'] = []
|
|
112
|
+
else
|
|
113
|
+
exception['errorClass'] = ex.message
|
|
114
|
+
exception['message'] = ex.message
|
|
115
|
+
exception['stacktrace'] = stack
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
exceptions.append(exception)
|
|
119
|
+
ev['exceptions'] = exceptions
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_app(ev)
|
|
123
|
+
app = {
|
|
124
|
+
'releaseStage' => @device_properties.rollout_environment,
|
|
125
|
+
'version' => @device_properties.lib_version
|
|
126
|
+
}
|
|
127
|
+
ev['app'] = app
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
STACK_TRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
|
|
131
|
+
|
|
132
|
+
def parse_stack_trace(stack_trace)
|
|
133
|
+
stack = []
|
|
134
|
+
stack_trace.each do |line|
|
|
135
|
+
match = line.match(STACK_TRACE_LINE_REGEX)
|
|
136
|
+
next if match.nil?
|
|
137
|
+
|
|
138
|
+
file = match[1]
|
|
139
|
+
line_str = match[2]
|
|
140
|
+
func = match[3]
|
|
141
|
+
stack << {
|
|
142
|
+
'file' => file,
|
|
143
|
+
'method' => func,
|
|
144
|
+
'lineNumber' => line_str.to_i,
|
|
145
|
+
'columnNumber' => 0
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
stack
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class CustomPropertyRepository
|
|
4
|
+
def initialize
|
|
5
|
+
@custom_properties = {}
|
|
6
|
+
@property_added_handlers = []
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
@handlers_mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_custom_property(custom_property)
|
|
12
|
+
return if custom_property.name.nil? || custom_property.name.empty?
|
|
13
|
+
|
|
14
|
+
@mutex.synchronize do
|
|
15
|
+
@custom_properties[custom_property.name] = custom_property
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
raise_property_added_event(custom_property)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_custom_property_if_not_exists(custom_property)
|
|
22
|
+
return if custom_property.name.nil? || custom_property.name.empty?
|
|
23
|
+
|
|
24
|
+
@mutex.synchronize do
|
|
25
|
+
return if @custom_properties.include?(custom_property.name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
add_custom_property(custom_property)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def custom_property(name)
|
|
32
|
+
@mutex.synchronize do
|
|
33
|
+
return @custom_properties[name]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def all_custom_properties
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
return @custom_properties.values
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def register_property_added_handler(&block)
|
|
44
|
+
@handlers_mutex.synchronize do
|
|
45
|
+
@property_added_handlers << block
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def raise_property_added_event(custom_property)
|
|
50
|
+
handlers = []
|
|
51
|
+
@handlers_mutex.synchronize do
|
|
52
|
+
handlers = @property_added_handlers.clone
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
handlers.each do |handler|
|
|
56
|
+
handler.call(custom_property)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class ExperimentRepository
|
|
4
|
+
def initialize
|
|
5
|
+
@experiments = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def experiments=(experiments)
|
|
9
|
+
@experiments = experiments
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def experiment_by_flag(flag_name)
|
|
13
|
+
@experiments.detect { |e| e.flags.include?(flag_name) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def all_experiments
|
|
17
|
+
@experiments
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class FlagRepository
|
|
4
|
+
def initialize
|
|
5
|
+
@variants = {}
|
|
6
|
+
@flag_added_handlers = []
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
@handlers_mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_flag(variant, name)
|
|
12
|
+
variant.name = name if variant.name.nil? || variant.name.empty?
|
|
13
|
+
@mutex.synchronize do
|
|
14
|
+
@variants[name] = variant
|
|
15
|
+
end
|
|
16
|
+
raise_flag_added_event(variant)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def flag(name)
|
|
20
|
+
@mutex.synchronize do
|
|
21
|
+
return @variants[name]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def all_flags
|
|
26
|
+
@mutex.synchronize do
|
|
27
|
+
return @variants.values
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def register_flag_added_handler(&block)
|
|
32
|
+
@handlers_mutex.synchronize do
|
|
33
|
+
@flag_added_handlers << block
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def raise_flag_added_event(flag)
|
|
38
|
+
handlers = []
|
|
39
|
+
@handlers_mutex.synchronize do
|
|
40
|
+
handlers = @flag_added_handlers.clone
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
handlers.each do |handler|
|
|
44
|
+
handler.call(flag)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
|
|
3
|
+
module Rox
|
|
4
|
+
module Core
|
|
5
|
+
class ExperimentsExtensions
|
|
6
|
+
def initialize(parser, target_groups_repository, flags_repository, experiment_repository)
|
|
7
|
+
@parser = parser
|
|
8
|
+
@target_groups_repository = target_groups_repository
|
|
9
|
+
@flags_repository = flags_repository
|
|
10
|
+
@experiment_repository = experiment_repository
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def extend
|
|
14
|
+
@parser.add_operator('mergeSeed') do |parser, stack, context|
|
|
15
|
+
seed1 = stack.pop
|
|
16
|
+
seed2 = stack.pop
|
|
17
|
+
stack.push("#{seed1}.#{seed2}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@parser.add_operator('isInPercentage') do |parser, stack, context|
|
|
21
|
+
percentage = stack.pop
|
|
22
|
+
seed = stack.pop
|
|
23
|
+
|
|
24
|
+
percentage = percentage.is_a?(Numeric) ? percentage : percentage.to_s.to_f
|
|
25
|
+
|
|
26
|
+
bucket = bucket(seed)
|
|
27
|
+
stack.push(bucket <= percentage)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@parser.add_operator('isInPercentageRange') do |parser, stack, context|
|
|
31
|
+
percentage_low = stack.pop
|
|
32
|
+
percentage_high = stack.pop
|
|
33
|
+
seed = stack.pop
|
|
34
|
+
|
|
35
|
+
percentage_low = percentage_low.is_a?(Numeric) ? percentage_low : percentage_low.to_s.to_f
|
|
36
|
+
percentage_high = percentage_high.is_a?(Numeric) ? percentage_high : percentage_high.to_s.to_f
|
|
37
|
+
|
|
38
|
+
bucket = bucket(seed)
|
|
39
|
+
stack.push(percentage_low <= bucket && bucket < percentage_high)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@parser.add_operator('flagValue') do |parser, stack, context|
|
|
43
|
+
feature_flag_identifier = stack.pop
|
|
44
|
+
result = Flag::FLAG_FALSE_VALUE
|
|
45
|
+
variant = @flags_repository.flag(feature_flag_identifier)
|
|
46
|
+
|
|
47
|
+
if !variant.nil?
|
|
48
|
+
result = variant.value(context)
|
|
49
|
+
else
|
|
50
|
+
flags_experiment = @experiment_repository.experiment_by_flag(feature_flag_identifier)
|
|
51
|
+
|
|
52
|
+
if !flags_experiment.nil? && !flags_experiment.condition.nil? && !flags_experiment.condition.empty?
|
|
53
|
+
experiment_eval_result = parser.evaluate_expression(flags_experiment.condition, context).string_value
|
|
54
|
+
result = experiment_eval_result if !experiment_eval_result.nil? && !experiment_eval_result.empty?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
stack.push(result)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@parser.add_operator('isInTargetGroup') do |parser, stack, context|
|
|
62
|
+
target_group_identifier = stack.pop
|
|
63
|
+
target_group = @target_groups_repository.target_group(target_group_identifier)
|
|
64
|
+
if target_group.nil?
|
|
65
|
+
stack.push(false)
|
|
66
|
+
else
|
|
67
|
+
stack.push(parser.evaluate_expression(target_group.condition, context).bool_value)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def bucket(seed)
|
|
73
|
+
bytes = Digest::MD5.digest(seed).bytes
|
|
74
|
+
hash = (bytes[0] & 0xFF) | ((bytes[1] & 0xFF) << 8) | ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 24)
|
|
75
|
+
hash &= 0xFFFFFFFF
|
|
76
|
+
bucket = hash * 1.0 / (2**32 - 1)
|
|
77
|
+
|
|
78
|
+
bucket == 1 ? 0 : bucket
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|