kameleoon-client-ruby 1.0.9 → 1.1.1
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/kameleoon/client.rb +62 -11
- data/lib/kameleoon/exceptions.rb +5 -0
- data/lib/kameleoon/query_graphql.rb +17 -7
- data/lib/kameleoon/request.rb +25 -1
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +1 -1
- data/lib/kameleoon/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e62c8e4dfa35f91706257ff5367a266736af7c03786803ad8f9fff06aa8c194
|
4
|
+
data.tar.gz: 7773e082be938108b42c65cd0109392413bf938619cf04cc994617111c746f15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d74b03762402a1f4c6e7349bb085e3b185ee564f0a7f645a16f7bb8da0321b73689adad785d52bdce48e9a0646f6b10de6c3f2fc8016b9459b713eafbda77e6f
|
7
|
+
data.tar.gz: a835f8b23265a2ae8241114fb2c5fe1f8965544fb348c2f6c16d714fe543fa6396b5bc6cbf7f68d8ac5e5391b8cbf594f55086e8c24290fda57a7b18ac5f57c4
|
data/lib/kameleoon/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'kameleoon/targeting/models'
|
2
4
|
require 'kameleoon/request'
|
3
5
|
require 'kameleoon/exceptions'
|
@@ -7,6 +9,8 @@ require 'rufus/scheduler'
|
|
7
9
|
require 'yaml'
|
8
10
|
require 'json'
|
9
11
|
require 'em-synchrony'
|
12
|
+
require 'em-synchrony/em-http'
|
13
|
+
require 'em-synchrony/fiber_iterator'
|
10
14
|
require 'objspace'
|
11
15
|
require 'time'
|
12
16
|
|
@@ -27,11 +31,14 @@ module Kameleoon
|
|
27
31
|
@site_code = site_code
|
28
32
|
@blocking = blocking
|
29
33
|
@default_timeout = config['default_timeout'] || default_timeout # in ms
|
30
|
-
|
34
|
+
refresh_interval = config['actions_configuration_refresh_interval']
|
35
|
+
@interval = refresh_interval.nil? ? interval : "#{refresh_interval}m"
|
31
36
|
@tracking_url = config['tracking_url'] || "https://api-ssx.kameleoon.com"
|
37
|
+
@api_data_url = "https://api-data.kameleoon.com"
|
32
38
|
@client_id = client_id || config['client_id']
|
33
39
|
@client_secret = client_secret || config['client_secret']
|
34
40
|
@data_maximum_size = config['visitor_data_maximum_size'] || 500 # mb
|
41
|
+
@environment = config['environment'] || DEFAULT_ENVIRONMENT
|
35
42
|
@verbose_mode = config['verbose_mode'] || false
|
36
43
|
@experiments = []
|
37
44
|
@feature_flags = []
|
@@ -119,6 +126,7 @@ module Kameleoon
|
|
119
126
|
end
|
120
127
|
variation_id.to_i
|
121
128
|
else
|
129
|
+
check_site_code_enable(experiment)
|
122
130
|
visitor_data = @data.select { |key, value| key.to_s == visitor_code }.values.flatten! || []
|
123
131
|
if experiment['targetingSegment'].nil? || experiment['targetingSegment'].check_tree(visitor_data)
|
124
132
|
threshold = obtain_hash_double(visitor_code, experiment['respoolTime'], experiment['id'])
|
@@ -262,6 +270,7 @@ module Kameleoon
|
|
262
270
|
result.to_s != "null"
|
263
271
|
|
264
272
|
else
|
273
|
+
check_site_code_enable(feature_flag)
|
265
274
|
visitor_data = @data.select { |key, value| key.to_s == visitor_code }.values.flatten! || []
|
266
275
|
unless feature_flag['targetingSegment'].nil? || feature_flag['targetingSegment'].check_tree(visitor_data)
|
267
276
|
raise Exception::NotTargeted.new(visitor_code)
|
@@ -269,7 +278,7 @@ module Kameleoon
|
|
269
278
|
|
270
279
|
if is_feature_flag_scheduled(feature_flag, Time.now.to_i)
|
271
280
|
threshold = obtain_hash_double(visitor_code, {}, id)
|
272
|
-
if threshold
|
281
|
+
if threshold >= 1 - feature_flag['expositionRate']
|
273
282
|
track_experiment(visitor_code, id, feature_flag["variations"].first['id'])
|
274
283
|
true
|
275
284
|
else
|
@@ -288,7 +297,7 @@ module Kameleoon
|
|
288
297
|
# A feature variable can be changed easily via our web application.
|
289
298
|
#
|
290
299
|
# @param [String | Integer] feature_key
|
291
|
-
# @param [String
|
300
|
+
# @param [String] variable_key
|
292
301
|
#
|
293
302
|
# @raise [Kameleoon::Exception::FeatureConfigurationNotFound]
|
294
303
|
# @raise [Kameleoon::Exception::FeatureVariableNotFound]
|
@@ -313,22 +322,48 @@ module Kameleoon
|
|
313
322
|
end
|
314
323
|
end
|
315
324
|
|
325
|
+
##
|
326
|
+
# The retrieved_data_from_remote_source method allows you to retrieve data (according to a key passed as argument)
|
327
|
+
# stored on a remote Kameleoon server. Usually data will be stored on our remote servers via the use of our Data API.
|
328
|
+
# This method, along with the availability of our highly scalable servers for this purpose, provides a convenient way
|
329
|
+
# to quickly store massive amounts of data that can be later retrieved for each of your visitors / users.
|
330
|
+
#
|
331
|
+
# @param [String] key Key you want to retrieve data. This field is mandatory.
|
332
|
+
# @param [Int] timeout Timeout for request. Equals default_timeout in a config file. This field is optional.
|
333
|
+
#
|
334
|
+
# @return [Hash] Hash object of the json object.
|
335
|
+
#
|
336
|
+
#
|
337
|
+
def retrieve_data_from_remote_source(key, timeout = @default_timeout)
|
338
|
+
connexion_options = { connect_timeout: (timeout.to_f / 1000.0) }
|
339
|
+
path = get_api_data_request_url(key)
|
340
|
+
log "Retrieve API Data connexion: #{connexion_options.inspect}"
|
341
|
+
response = get_sync(@api_data_url + path, connexion_options)
|
342
|
+
if is_successful_sync(response)
|
343
|
+
JSON.parse(response.body) unless response.nil?
|
344
|
+
else
|
345
|
+
return nil
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
316
349
|
private
|
317
|
-
|
350
|
+
|
351
|
+
API_SSX_URL = 'https://api-ssx.kameleoon.com'
|
318
352
|
REFERENCE = 0
|
319
|
-
STATUS_ACTIVE =
|
320
|
-
FEATURE_STATUS_DEACTIVATED =
|
353
|
+
STATUS_ACTIVE = 'ACTIVE'
|
354
|
+
FEATURE_STATUS_DEACTIVATED = 'DEACTIVATED'
|
355
|
+
DEFAULT_ENVIRONMENT = 'production'
|
321
356
|
attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
|
322
357
|
:blocking, :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
|
323
358
|
|
324
359
|
def fetch_configuration
|
325
360
|
@scheduler = Rufus::Scheduler.singleton
|
326
361
|
@scheduler.every @interval do
|
327
|
-
log(
|
362
|
+
log('Scheduled job to fetch configuration is starting.')
|
328
363
|
fetch_configuration_job_graphql
|
329
364
|
end
|
330
365
|
@scheduler.schedule '0s' do
|
331
|
-
log(
|
366
|
+
log('Start-up, fetching is starting')
|
332
367
|
fetch_configuration_job_graphql
|
333
368
|
end
|
334
369
|
end
|
@@ -337,7 +372,7 @@ module Kameleoon
|
|
337
372
|
EM.synchrony do
|
338
373
|
obtain_access_token
|
339
374
|
@experiments = obtain_experiments_graphql(@site_code) || @experiments
|
340
|
-
@feature_flags = obtain_feature_flags_graphql(@site_code) || @feature_flags
|
375
|
+
@feature_flags = obtain_feature_flags_graphql(@site_code, @environment) || @feature_flags
|
341
376
|
EM.stop
|
342
377
|
end
|
343
378
|
end
|
@@ -479,9 +514,9 @@ module Kameleoon
|
|
479
514
|
experiments
|
480
515
|
end
|
481
516
|
|
482
|
-
def obtain_feature_flags_graphql(site_id, per_page = -1)
|
517
|
+
def obtain_feature_flags_graphql(site_id, environment = @environment, per_page = -1)
|
483
518
|
log "Fetching feature flags GraphQL"
|
484
|
-
feature_flags = fetch_all_graphql("v1/graphql?perPage=#{per_page}", Kameleoon::Query.query_feature_flags(site_code)).map { |it| JSON.parse(it.response)['data']['featureFlags']['edges'] }.flatten.map do |feature_flag|
|
519
|
+
feature_flags = fetch_all_graphql("v1/graphql?perPage=#{per_page}", Kameleoon::Query.query_feature_flags(site_code, environment)).map { |it| JSON.parse(it.response)['data']['featureFlags']['edges'] }.flatten.map do |feature_flag|
|
485
520
|
complete_campaign_graphql(feature_flag['node'])
|
486
521
|
end
|
487
522
|
log "Feature flags are fetched: " + feature_flags.inspect
|
@@ -575,11 +610,21 @@ module Kameleoon
|
|
575
610
|
"/dataTracking?" + URI.encode_www_form(get_common_ssx_parameters(visitor_code))
|
576
611
|
end
|
577
612
|
|
613
|
+
def get_api_data_request_url(key)
|
614
|
+
mapKey = {
|
615
|
+
siteCode: site_code,
|
616
|
+
key: key
|
617
|
+
}
|
618
|
+
"/data?#{URI.encode_www_form(mapKey)}"
|
619
|
+
end
|
620
|
+
|
578
621
|
def get_feature_flag(feature_key)
|
579
622
|
if feature_key.is_a?(String)
|
580
623
|
feature_flag = @feature_flags.select { |ff| ff['identificationKey'] == feature_key}.first
|
581
624
|
elsif feature_key.is_a?(Integer)
|
582
625
|
feature_flag = @feature_flags.select { |ff| ff['id'].to_i == feature_key}.first
|
626
|
+
print "\nPassing `feature_key` with type of `int` to `activate_feature` or `obtain_feature_variable` "\
|
627
|
+
"is deprecated, it will be removed in next releases. This is necessary to support multi-environment feature\n"
|
583
628
|
else
|
584
629
|
raise TypeError.new("Feature key should be a String or an Integer.")
|
585
630
|
end
|
@@ -671,6 +716,12 @@ module Kameleoon
|
|
671
716
|
end
|
672
717
|
end
|
673
718
|
|
719
|
+
def check_site_code_enable(exp_or_ff)
|
720
|
+
unless exp_or_ff['site'].nil? || exp_or_ff['site']['isKameleoonEnabled']
|
721
|
+
raise Exception::SiteCodeDisabled.new(site_code)
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
674
725
|
def data_not_sent(visitor_code = nil)
|
675
726
|
if visitor_code.nil?
|
676
727
|
@data.select {|key, values| values.any? {|data| !data.sent}}
|
data/lib/kameleoon/exceptions.rb
CHANGED
@@ -4,7 +4,7 @@ module Kameleoon
|
|
4
4
|
def self.query_experiments(site_code)
|
5
5
|
'{
|
6
6
|
"operationName": "getExperiments",
|
7
|
-
"query": "query getExperiments($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { experiments(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name type site { id code } status variations { id customJson } deviations { variationId value } respoolTime {variationId value } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
|
7
|
+
"query": "query getExperiments($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { experiments(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name type site { id code isKameleoonEnabled } status variations { id customJson } deviations { variationId value } respoolTime {variationId value } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
|
8
8
|
"variables": {
|
9
9
|
"filter": {
|
10
10
|
"and": [{
|
@@ -13,13 +13,15 @@ module Kameleoon
|
|
13
13
|
"operator": "IN",
|
14
14
|
"parameters": ["ACTIVE", "DEVIATED", "USED_AS_PERSONALIZATION"]
|
15
15
|
}
|
16
|
-
},
|
16
|
+
},
|
17
|
+
{
|
17
18
|
"condition": {
|
18
19
|
"field": "type",
|
19
20
|
"operator": "IN",
|
20
21
|
"parameters": ["SERVER_SIDE", "HYBRID"]
|
21
22
|
}
|
22
|
-
},
|
23
|
+
},
|
24
|
+
{
|
23
25
|
"condition": {
|
24
26
|
"field": "siteCode",
|
25
27
|
"operator": "IN",
|
@@ -35,10 +37,10 @@ module Kameleoon
|
|
35
37
|
}'
|
36
38
|
end
|
37
39
|
|
38
|
-
def self.query_feature_flags(site_code)
|
40
|
+
def self.query_feature_flags(site_code, environment)
|
39
41
|
'{
|
40
42
|
"operationName": "getFeatureFlags",
|
41
|
-
"query": "query getFeatureFlags($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { featureFlags(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name site { id code } bypassDeviation status variations { id customJson } respoolTime { variationId value } expositionRate identificationKey featureFlagSdkLanguageType featureStatus schedules { dateStart dateEnd } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
|
43
|
+
"query": "query getFeatureFlags($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { featureFlags(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name site { id code isKameleoonEnabled } bypassDeviation status variations { id customJson } respoolTime { variationId value } expositionRate identificationKey featureFlagSdkLanguageType featureStatus schedules { dateStart dateEnd } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
|
42
44
|
"variables": {
|
43
45
|
"filter": {
|
44
46
|
"and": [{
|
@@ -47,13 +49,21 @@ module Kameleoon
|
|
47
49
|
"operator": "IN",
|
48
50
|
"parameters": ["ACTIVATED", "SCHEDULED", "DEACTIVATED"]
|
49
51
|
}
|
50
|
-
},
|
52
|
+
},
|
53
|
+
{
|
51
54
|
"condition": {
|
52
55
|
"field": "siteCode",
|
53
56
|
"operator": "IN",
|
54
57
|
"parameters": ["' + site_code + '"]
|
55
58
|
}
|
56
|
-
}
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"condition": {
|
62
|
+
"field": "environment.key",
|
63
|
+
"operator": "IN",
|
64
|
+
"parameters": ["' + environment + '"]
|
65
|
+
}
|
66
|
+
}]
|
57
67
|
},
|
58
68
|
"sort": [{
|
59
69
|
"field": "id",
|
data/lib/kameleoon/request.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "em-synchrony/em-http"
|
2
2
|
require "kameleoon/version"
|
3
|
+
require 'net/http'
|
3
4
|
|
4
5
|
module Kameleoon
|
5
6
|
# @api private
|
@@ -20,6 +21,10 @@ module Kameleoon
|
|
20
21
|
request(Method::POST, request_options, url, connexion_options)
|
21
22
|
end
|
22
23
|
|
24
|
+
def get_sync(url = API_URL, connexion_options = {})
|
25
|
+
request_sync(Method::GET, url, connexion_options)
|
26
|
+
end
|
27
|
+
|
23
28
|
private
|
24
29
|
|
25
30
|
def request(method, request_options, url, connexion_options)
|
@@ -29,7 +34,22 @@ module Kameleoon
|
|
29
34
|
when Method::POST then
|
30
35
|
return EventMachine::HttpRequest.new(url, connexion_options).apost request_options
|
31
36
|
when Method::GET then
|
32
|
-
return EventMachine::HttpRequest.new(url, connexion_options).
|
37
|
+
return EventMachine::HttpRequest.new(url, connexion_options).get request_options
|
38
|
+
else
|
39
|
+
print "Unknown request type"
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_sync(method, url, connexion_options)
|
45
|
+
request_options = {} if request_options.nil?
|
46
|
+
add_user_agent(request_options)
|
47
|
+
case method
|
48
|
+
when Method::GET then
|
49
|
+
uri = URI(url)
|
50
|
+
request = Net::HTTP.new(url)
|
51
|
+
response = Net::HTTP.get_response(uri)
|
52
|
+
response
|
33
53
|
else
|
34
54
|
print "Unknown request type"
|
35
55
|
return false
|
@@ -40,6 +60,10 @@ module Kameleoon
|
|
40
60
|
!request.nil? && request != false && /20\d/.match(request.response_header.status.to_s)
|
41
61
|
end
|
42
62
|
|
63
|
+
def is_successful_sync(response)
|
64
|
+
!response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
|
65
|
+
end
|
66
|
+
|
43
67
|
def add_user_agent(request_options)
|
44
68
|
if request_options[:head].nil?
|
45
69
|
request_options[:head] = {'Kameleoon-Client' => 'sdk/ruby/' + Kameleoon::VERSION}
|
@@ -34,7 +34,7 @@ module Kameleoon
|
|
34
34
|
|
35
35
|
def check(datas)
|
36
36
|
is_targeted = false
|
37
|
-
custom_data = datas.select { |data| data.instance == DataType::CUSTOM && data.id == @index }.
|
37
|
+
custom_data = datas.select { |data| data.instance == DataType::CUSTOM && data.id == @index }.last
|
38
38
|
if custom_data.nil?
|
39
39
|
is_targeted = (@operator == Operator::UNDEFINED.to_s)
|
40
40
|
else
|
data/lib/kameleoon/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kameleoon-client-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|