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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7e084eaecf8e5963ba3977a3b23e4618b0780a51f804b679f7663003a954f45
4
- data.tar.gz: be0d11c361c5076c6f0fb24753a733003cd7cb08df2e5a5b1faca1627894f76d
3
+ metadata.gz: 9cdbccf7de079d1b5b692c6eac4fc313c9abcb389feeabfd7a911cba1d6644c9
4
+ data.tar.gz: fb964940dca8a652dab2023fddab01ea7f1fce624d43d645089f07b9d2012fc3
5
5
  SHA512:
6
- metadata.gz: 6e1e42b99883f208f8a580fb58e709f067104f4646a59a8aba25492cf3a616b376c785ac4907f30cf171165564b24c161133a2ba8a4c5cb09146a181016fd141
7
- data.tar.gz: 0c78c3018c22f8d81c0cd8818c74ba47806aabf5a59612816414c1e1667def427bb232afeea70b6cbd0cb59ad33517236cedc008a1c500812fe1a63c6caa6e03
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 = @features.find { |f| f["name"] == name }
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["id"], context[:ip])
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
- if interval > 0
58
- scheduler = Rufus::Scheduler.new
59
- scheduler.every "#{interval}.s" do
60
- fetch_features
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 fetch_features
67
- url = "#{config[:server_address]}/api/feature"
68
- last_modified = Time.now - config[:interval]
65
+ def disconnect
66
+ # Stop the previous process if it exists
67
+ @process_thread&.kill
68
+ end
69
69
 
70
- response =
71
- HTTParty.get(
72
- url,
73
- options: {
74
- headers: {
75
- "If-Modified-Since" => last_modified.rfc2822,
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
- puts response.parsed_response
80
- self.features = response.parsed_response if response.code == 200
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 = features.find { |f| f["name"] == name }
18
- raise TypeError, "Provided name does not match any feature." if feature.nil?
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
- type_3_features =
38
- features.filter do |f|
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
- type_3_features.each do |exp|
49
- segment_end += exp["user_percentage"]
50
- if hashed_id >= segment_start && hashed_id <= segment_end &&
51
- exp["name"] == name
52
- return true
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 = features.find { |f| f["name"] == name }
67
- raise TypeError, "Provided name does not match any feature." unless feature
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
- type3features = features.select { |f| f["type_id"] == 3 }
71
- segment_start, segment_end = 0, 0
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
- type3features.each do |exp|
74
- segment_end += exp["user_percentage"]
75
- if hashed_id >= segment_start && hashed_id <= segment_end &&
76
- exp["name"] == name
77
- running_total = segment_start
78
- variants.each do |variant|
79
- running_total += variant["weight"].to_f * variant["weight"].to_f
80
- return variant if hashed_id <= running_total
81
- end
82
- else
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
- return false
82
+ false
88
83
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestlabSdkRuby
4
- VERSION = "0.2.0"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -13,7 +13,7 @@ class Config
13
13
  def connect
14
14
  client =
15
15
  Client.new({ server_address: @server_address, interval: @interval })
16
- client.get_features
16
+ client.fetch_features
17
17
  client.add_default_context
18
18
  client.timed_fetch(@interval)
19
19
  client
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.2.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
  - ''