testlab_sdk_ruby 0.2.0 → 1.0.0
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 +4 -4
- data/lib/testlab_sdk_ruby/testlab_client.rb +38 -35
- data/lib/testlab_sdk_ruby/testlab_feature_logic.rb +37 -42
- data/lib/testlab_sdk_ruby/version.rb +1 -1
- data/lib/testlab_sdk_ruby.rb +1 -1
- metadata +1 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cdbccf7de079d1b5b692c6eac4fc313c9abcb389feeabfd7a911cba1d6644c9
|
4
|
+
data.tar.gz: fb964940dca8a652dab2023fddab01ea7f1fce624d43d645089f07b9d2012fc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3232acc7c5dd89febac9ad7e54a2bacaaffa5406fef46790a29766548f9ace8dbfcbfd2fdbddb2aad3ca609c212ce4d086f6b462470e5d63ca55ecdb43b03778
|
7
|
+
data.tar.gz: 7a331403d159150e012b8643501029eaaafee1f6a9ebcedfc95e734e2af41e8375dca55de007846422d02883b994057cbbc62bce136f4eac28a98125543311df
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require "testlab_sdk_ruby/testlab_feature_logic"
|
2
2
|
require "securerandom" # uuid = SecureRandom.uuid
|
3
3
|
require "httparty"
|
4
|
-
require "rufus-scheduler"
|
5
4
|
|
6
5
|
class Client
|
7
6
|
attr_accessor :config, :context, :features
|
@@ -10,6 +9,7 @@ class Client
|
|
10
9
|
@config = config
|
11
10
|
@context = nil
|
12
11
|
@features = {}
|
12
|
+
@process_thread = nil
|
13
13
|
end
|
14
14
|
|
15
15
|
def add_default_context
|
@@ -26,14 +26,17 @@ class Client
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def get_feature_value(name)
|
29
|
-
feature =
|
29
|
+
feature =
|
30
|
+
features["experiments"]
|
31
|
+
.concat(features["toggles"], features["rollouts"])
|
32
|
+
.find { |f| f["name"] == name }
|
33
|
+
|
30
34
|
return false unless feature
|
31
35
|
|
32
36
|
if feature["type_id"] != 3
|
33
37
|
return is_enabled(features, name, context[:user_id])
|
34
38
|
else
|
35
39
|
enabled = is_enabled(features, name, context[:user_id])
|
36
|
-
# return false unless enabled
|
37
40
|
variant = get_variant(features, name, context[:user_id])
|
38
41
|
users = get_users
|
39
42
|
existing_user =
|
@@ -41,43 +44,49 @@ class Client
|
|
41
44
|
user["id"] == context[:user_id] && user["variant_id"] == variant["id"]
|
42
45
|
end
|
43
46
|
if enabled && variant && !existing_user
|
44
|
-
create_user(context[:user_id], variant[
|
47
|
+
create_user(context[:user_id], variant[:id], context[:ip])
|
45
48
|
end
|
46
49
|
enabled && variant
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
50
|
-
def get_features
|
51
|
-
url = "#{config[:server_address]}/api/feature"
|
52
|
-
response = HTTParty.get(url)
|
53
|
-
self.features = response.parsed_response
|
54
|
-
end
|
55
|
-
|
56
53
|
def timed_fetch(interval)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
disconnect
|
55
|
+
|
56
|
+
@process_thread =
|
57
|
+
Thread.new do
|
58
|
+
loop do
|
59
|
+
fetch_features
|
60
|
+
sleep interval
|
61
|
+
end
|
61
62
|
end
|
62
|
-
Thread.new { scheduler.join }
|
63
|
-
end
|
64
63
|
end
|
65
64
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
65
|
+
def disconnect
|
66
|
+
# Stop the previous process if it exists
|
67
|
+
@process_thread&.kill
|
68
|
+
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
70
|
+
def fetch_features
|
71
|
+
url = "#{config[:server_address]}/api/feature/current"
|
72
|
+
|
73
|
+
if features
|
74
|
+
last_modified = Time.now - config[:interval]
|
75
|
+
|
76
|
+
response =
|
77
|
+
HTTParty.get(
|
78
|
+
url,
|
79
|
+
options: {
|
80
|
+
headers: {
|
81
|
+
"If-Modified-Since" => last_modified.rfc2822,
|
82
|
+
},
|
76
83
|
},
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
84
|
+
)
|
85
|
+
self.features = response.parsed_response if response.code == 200
|
86
|
+
else
|
87
|
+
response = HTTParty.get(url)
|
88
|
+
self.features = response.parsed_response
|
89
|
+
end
|
81
90
|
end
|
82
91
|
|
83
92
|
def get_users
|
@@ -132,9 +141,3 @@ class Client
|
|
132
141
|
end
|
133
142
|
end
|
134
143
|
end
|
135
|
-
|
136
|
-
# myClient = Client.new({ server_address: "http://localhost:3000", interval: 10 })
|
137
|
-
# myClient.add_default_context
|
138
|
-
# myClient.get_features
|
139
|
-
# puts myClient.context[:user_id]
|
140
|
-
# puts myClient.get_feature_value("new_experiment")
|
@@ -14,75 +14,70 @@ end
|
|
14
14
|
|
15
15
|
def is_enabled(features, name, user_id)
|
16
16
|
# Find target feature based on name
|
17
|
-
feature =
|
18
|
-
|
17
|
+
feature =
|
18
|
+
features["experiments"]
|
19
|
+
.concat(features["toggles"], features["rollouts"])
|
20
|
+
.find { |f| f["name"] == name }
|
21
|
+
# feature = features.find { |f| f["name"] == name }
|
22
|
+
return false unless feature
|
19
23
|
|
20
24
|
# Return false if current date is outside of date range for feature
|
21
25
|
start_date = DateTime.parse(feature["start_date"])
|
22
26
|
end_date = DateTime.parse(feature["end_date"])
|
23
27
|
|
24
|
-
return false unless is_active?(start_date, end_date)
|
28
|
+
return false unless is_active?(start_date, end_date) && feature["is_running"]
|
25
29
|
|
26
30
|
# Return false if feature is not running (toggled off) or if the hashed ID is outside of the target user_percentage range
|
27
31
|
# For Type 3 (features), users can only be assigned to one feature (total percentage of users enrolled in features can not exceed 100%)
|
28
32
|
|
29
33
|
case feature["type_id"]
|
30
|
-
when 1
|
31
|
-
feature["is_running"]
|
32
34
|
when 2
|
33
35
|
hashed_id = hash_message(user_id + name)
|
34
36
|
feature["is_running"] && hashed_id < feature["user_percentage"]
|
35
37
|
when 3
|
36
38
|
hashed_id = hash_message(user_id)
|
37
|
-
|
38
|
-
|
39
|
-
f["type_id"] == 3 &&
|
40
|
-
is_active?(
|
41
|
-
DateTime.parse(f["start_date"]),
|
42
|
-
DateTime.parse(f["end_date"]),
|
43
|
-
)
|
44
|
-
end
|
45
|
-
segment_start = 0
|
46
|
-
segment_end = 0
|
39
|
+
blocks = features["userblocks"]
|
40
|
+
block_id = (hashed_id * blocks.length).ceil
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
else
|
54
|
-
segment_start = segment_end
|
55
|
-
end
|
56
|
-
end
|
42
|
+
!blocks
|
43
|
+
.filter { |b| b["id"] == block_id && b["feature_id"] == feature["id"] }
|
44
|
+
.empty?
|
45
|
+
else
|
46
|
+
false
|
57
47
|
end
|
58
|
-
|
59
|
-
false
|
60
48
|
end
|
61
49
|
|
62
50
|
def get_variant(features, name, user_id)
|
63
51
|
hashed_id = hash_message(user_id)
|
64
52
|
puts "uuid, hashed #{user_id}, #{hashed_id}"
|
65
53
|
|
66
|
-
feature =
|
67
|
-
|
54
|
+
feature =
|
55
|
+
features["experiments"]
|
56
|
+
.concat(features["toggles"], features["rollouts"])
|
57
|
+
.find { |f| f["name"] == name }
|
58
|
+
return false unless feature
|
68
59
|
|
69
60
|
variants = feature["variant_arr"]
|
70
|
-
|
71
|
-
|
61
|
+
blocks = features["userblocks"]
|
62
|
+
block_id = (hashed_id * blocks.length).ceil
|
63
|
+
|
64
|
+
target_block =
|
65
|
+
blocks
|
66
|
+
.filter { |b| b["id"] == block_id && b["feature_id"] == feature["id"] }
|
67
|
+
.first
|
72
68
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
segment_start = segment_end
|
69
|
+
return false unless target_block
|
70
|
+
|
71
|
+
segment_end = target_block["id"].to_f / blocks.length
|
72
|
+
segment_start = segment_end - 1.0 / blocks.length
|
73
|
+
|
74
|
+
running_total = segment_start
|
75
|
+
variants.each do |variant|
|
76
|
+
running_total += variant["weight"].to_f * (1.0 / blocks.length)
|
77
|
+
if hashed_id <= running_total
|
78
|
+
return { id: variant["id"], value: variant["value"] }
|
84
79
|
end
|
85
80
|
end
|
86
81
|
|
87
|
-
|
82
|
+
false
|
88
83
|
end
|
data/lib/testlab_sdk_ruby.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testlab_sdk_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alison Martinez
|
@@ -27,20 +27,6 @@ dependencies:
|
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
|
-
- !ruby/object:Gem::Dependency
|
31
|
-
name: rufus-scheduler
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
requirements:
|
34
|
-
- - ">="
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '0'
|
37
|
-
type: :runtime
|
38
|
-
prerelease: false
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
40
|
-
requirements:
|
41
|
-
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: '0'
|
44
30
|
description:
|
45
31
|
email:
|
46
32
|
- ''
|