kameleoon-client-ruby 1.0.7 → 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -19
- data/lib/kameleoon/client.rb +159 -24
- data/lib/kameleoon/cookie.rb +10 -1
- data/lib/kameleoon/data.rb +14 -4
- data/lib/kameleoon/exceptions.rb +6 -1
- data/lib/kameleoon/query_graphql.rb +76 -0
- data/lib/kameleoon/targeting/condition_factory.rb +0 -1
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +9 -10
- data/lib/kameleoon/targeting/models.rb +1 -1
- data/lib/kameleoon/targeting/tree_builder.rb +0 -1
- data/lib/kameleoon/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b70d1fc8cd47248dd68ba217e9a860577008e008cac7e6f562ab1856b3dc24c3
|
4
|
+
data.tar.gz: 03ddd4ce4f1ea480bc5adfc347c9f2168af952cc9036ce5cff494af9699a4dfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3d39ad2e27a0312ff090f58a93cba6aa69450fd7763fbdc1375bd146bf1af909dde84815073fc7cf7d60b8aa123cb4cc139e8dee19c30aba3a88ae021475633
|
7
|
+
data.tar.gz: bfb92b7c42f02fde8d90484efef51abe294eaece630e97e7d0168e7974dd89d06b9069e9226c23bc2cf25755a2c1d4e41c87d114a1093b6e391a04d440f60de7
|
data/README.md
CHANGED
@@ -1,22 +1,7 @@
|
|
1
|
-
# Kameleoon
|
1
|
+
# Kameleoon Ruby SDK
|
2
2
|
|
3
|
-
|
3
|
+
## Getting Started
|
4
4
|
|
5
|
-
|
6
|
-
#### Prerequisite:
|
7
|
-
* [Install ruby](https://www.ruby-lang.org/en/documentation/installation)
|
5
|
+
Our SDK gives you the possibility of running experiments and activating feature flags on your Ruby platform. Integrating our SDK into your applications is easy, and its footprint (in terms of memory and network usage) is low.
|
8
6
|
|
9
|
-
|
10
|
-
* Run `./buildAndInstallGem.sh`
|
11
|
-
|
12
|
-
### How to run tests
|
13
|
-
#### Prerequisite:
|
14
|
-
* Build and install Kameleoon Gem locally (infos above).
|
15
|
-
* Install rake: `gem install rake`
|
16
|
-
|
17
|
-
#### Run Tests:
|
18
|
-
##### Unit
|
19
|
-
* Run `./test.sh -u`
|
20
|
-
##### Integration
|
21
|
-
Make sure you kill manually the server test app after the tests are done.
|
22
|
-
* Run `./test.sh -i`
|
7
|
+
You can refer to the [SDK reference](https://developers.kameleoon.com/ruby-sdk.html#reference) to check out all possible features of the SDK. Also make sure you check out our [Getting started tutorial](https://developers.kameleoon.com/ruby-sdk.html#getting-started) which we have prepared to walk you through the installation and implementation.
|
data/lib/kameleoon/client.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'kameleoon/targeting/models'
|
2
4
|
require 'kameleoon/request'
|
3
5
|
require 'kameleoon/exceptions'
|
4
6
|
require 'kameleoon/cookie'
|
7
|
+
require 'kameleoon/query_graphql'
|
5
8
|
require 'rufus/scheduler'
|
6
9
|
require 'yaml'
|
7
10
|
require 'json'
|
8
11
|
require 'em-synchrony'
|
9
12
|
require 'objspace'
|
13
|
+
require 'time'
|
10
14
|
|
11
15
|
module Kameleoon
|
12
16
|
##
|
@@ -30,6 +34,7 @@ module Kameleoon
|
|
30
34
|
@client_id = client_id || config['client_id']
|
31
35
|
@client_secret = client_secret || config['client_secret']
|
32
36
|
@data_maximum_size = config['visitor_data_maximum_size'] || 500 # mb
|
37
|
+
@environment = config['environment'] || DEFAULT_ENVIRONMENT
|
33
38
|
@verbose_mode = config['verbose_mode'] || false
|
34
39
|
@experiments = []
|
35
40
|
@feature_flags = []
|
@@ -83,8 +88,10 @@ module Kameleoon
|
|
83
88
|
# @raise [Kameleoon::Exception::ExperimentConfigurationNotFound] Raise when experiment configuration is not found
|
84
89
|
# @raise [Kameleoon::Exception::NotActivated] The visitor triggered the experiment, but did not activate it. Usually, this happens because the user has been associated with excluded traffic
|
85
90
|
# @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the associated targeting segment conditions were not fulfilled. He should see the reference variation
|
91
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
86
92
|
#
|
87
93
|
def trigger_experiment(visitor_code, experiment_id, timeout = @default_timeout)
|
94
|
+
check_visitor_code(visitor_code)
|
88
95
|
experiment = @experiments.find { |experiment| experiment['id'].to_s == experiment_id.to_s }
|
89
96
|
if experiment.nil?
|
90
97
|
raise Exception::ExperimentConfigurationNotFound.new(experiment_id)
|
@@ -115,6 +122,7 @@ module Kameleoon
|
|
115
122
|
end
|
116
123
|
variation_id.to_i
|
117
124
|
else
|
125
|
+
check_site_code_enable(experiment)
|
118
126
|
visitor_data = @data.select { |key, value| key.to_s == visitor_code }.values.flatten! || []
|
119
127
|
if experiment['targetingSegment'].nil? || experiment['targetingSegment'].check_tree(visitor_data)
|
120
128
|
threshold = obtain_hash_double(visitor_code, experiment['respoolTime'], experiment['id'])
|
@@ -143,7 +151,10 @@ module Kameleoon
|
|
143
151
|
# @param [String] visitor_code Visitor code
|
144
152
|
# @param [...Data] data Data to associate with the visitor code
|
145
153
|
#
|
154
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
155
|
+
#
|
146
156
|
def add_data(visitor_code, *args)
|
157
|
+
check_visitor_code(visitor_code)
|
147
158
|
while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20) do
|
148
159
|
@data.shift
|
149
160
|
end
|
@@ -167,7 +178,10 @@ module Kameleoon
|
|
167
178
|
# @param [Integer] goal_id Id of the goal
|
168
179
|
# @param [Float] revenue Optional - Revenue of the conversion.
|
169
180
|
#
|
181
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
182
|
+
#
|
170
183
|
def track_conversion(visitor_code, goal_id, revenue = 0.0)
|
184
|
+
check_visitor_code(visitor_code)
|
171
185
|
add_data(visitor_code, Conversion.new(goal_id, revenue))
|
172
186
|
flush(visitor_code)
|
173
187
|
end
|
@@ -181,7 +195,10 @@ module Kameleoon
|
|
181
195
|
#
|
182
196
|
# @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
|
183
197
|
#
|
198
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is not null and empty or longer than 255 chars
|
199
|
+
#
|
184
200
|
def flush(visitor_code = nil)
|
201
|
+
check_visitor_code(visitor_code) unless visitor_code.nil?
|
185
202
|
track_data(visitor_code)
|
186
203
|
end
|
187
204
|
|
@@ -203,9 +220,9 @@ module Kameleoon
|
|
203
220
|
def obtain_variation_associated_data(variation_id)
|
204
221
|
variation = @experiments.map { |experiment| experiment['variations'] }.flatten.select { |variation| variation['id'].to_i == variation_id.to_i }.first
|
205
222
|
if variation.nil?
|
206
|
-
raise Exception::
|
223
|
+
raise Exception::VariationConfigurationNotFound.new(variation_id)
|
207
224
|
else
|
208
|
-
variation['customJson']
|
225
|
+
JSON.parse(variation['customJson'])
|
209
226
|
end
|
210
227
|
end
|
211
228
|
|
@@ -221,8 +238,10 @@ module Kameleoon
|
|
221
238
|
#
|
222
239
|
# @raise [Kameleoon::Exception::FeatureConfigurationNotFound]
|
223
240
|
# @raise [Kameleoon::Exception::NotTargeted]
|
241
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
224
242
|
#
|
225
243
|
def activate_feature(visitor_code, feature_key, timeout = @default_timeout)
|
244
|
+
check_visitor_code(visitor_code)
|
226
245
|
feature_flag = get_feature_flag(feature_key)
|
227
246
|
id = feature_flag['id']
|
228
247
|
if @blocking
|
@@ -247,18 +266,23 @@ module Kameleoon
|
|
247
266
|
result.to_s != "null"
|
248
267
|
|
249
268
|
else
|
269
|
+
check_site_code_enable(feature_flag)
|
250
270
|
visitor_data = @data.select { |key, value| key.to_s == visitor_code }.values.flatten! || []
|
251
|
-
|
271
|
+
unless feature_flag['targetingSegment'].nil? || feature_flag['targetingSegment'].check_tree(visitor_data)
|
272
|
+
raise Exception::NotTargeted.new(visitor_code)
|
273
|
+
end
|
274
|
+
|
275
|
+
if is_feature_flag_scheduled(feature_flag, Time.now.to_i)
|
252
276
|
threshold = obtain_hash_double(visitor_code, {}, id)
|
253
|
-
if threshold
|
254
|
-
track_experiment(visitor_code, id, feature_flag["
|
255
|
-
|
277
|
+
if threshold >= 1 - feature_flag['expositionRate']
|
278
|
+
track_experiment(visitor_code, id, feature_flag["variations"].first['id'])
|
279
|
+
true
|
256
280
|
else
|
257
281
|
track_experiment(visitor_code, id, REFERENCE, true)
|
258
|
-
|
282
|
+
false
|
259
283
|
end
|
260
284
|
else
|
261
|
-
|
285
|
+
false
|
262
286
|
end
|
263
287
|
end
|
264
288
|
end
|
@@ -276,39 +300,52 @@ module Kameleoon
|
|
276
300
|
#
|
277
301
|
def obtain_feature_variable(feature_key, variable_key)
|
278
302
|
feature_flag = get_feature_flag(feature_key)
|
279
|
-
custom_json = feature_flag["variations"].first['customJson'][variable_key.to_s]
|
303
|
+
custom_json = JSON.parse(feature_flag["variations"].first['customJson'])[variable_key.to_s]
|
280
304
|
if custom_json.nil?
|
281
305
|
raise Exception::FeatureVariableNotFound.new("Feature variable not found")
|
282
306
|
end
|
283
307
|
case custom_json['type']
|
284
308
|
when "Boolean"
|
285
|
-
return
|
309
|
+
return custom_json['value'].downcase == "true"
|
286
310
|
when "String"
|
287
|
-
return custom_json['value'].to_s
|
288
|
-
when "Number"
|
289
|
-
return custom_json['value'].to_f
|
290
|
-
when "Json"
|
291
311
|
return custom_json['value']
|
312
|
+
when "Number"
|
313
|
+
return custom_json['value'].to_i
|
314
|
+
when "JSON"
|
315
|
+
return JSON.parse(custom_json['value'])
|
292
316
|
else
|
293
317
|
raise TypeError.new("Unknown type for feature variable")
|
294
318
|
end
|
295
319
|
end
|
296
320
|
|
297
321
|
private
|
298
|
-
|
322
|
+
|
323
|
+
API_SSX_URL = 'https://api-ssx.kameleoon.com'
|
299
324
|
REFERENCE = 0
|
325
|
+
STATUS_ACTIVE = 'ACTIVE'
|
326
|
+
FEATURE_STATUS_DEACTIVATED = 'DEACTIVATED'
|
327
|
+
DEFAULT_ENVIRONMENT = 'production'
|
300
328
|
attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
|
301
329
|
:blocking, :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
|
302
330
|
|
303
331
|
def fetch_configuration
|
304
332
|
@scheduler = Rufus::Scheduler.singleton
|
305
333
|
@scheduler.every @interval do
|
306
|
-
log(
|
307
|
-
|
334
|
+
log('Scheduled job to fetch configuration is starting.')
|
335
|
+
fetch_configuration_job_graphql
|
308
336
|
end
|
309
337
|
@scheduler.schedule '0s' do
|
310
|
-
log(
|
311
|
-
|
338
|
+
log('Start-up, fetching is starting')
|
339
|
+
fetch_configuration_job_graphql
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def fetch_configuration_job_graphql
|
344
|
+
EM.synchrony do
|
345
|
+
obtain_access_token
|
346
|
+
@experiments = obtain_experiments_graphql(@site_code) || @experiments
|
347
|
+
@feature_flags = obtain_feature_flags_graphql(@site_code, @environment) || @feature_flags
|
348
|
+
EM.stop
|
312
349
|
end
|
313
350
|
end
|
314
351
|
|
@@ -387,8 +424,8 @@ module Kameleoon
|
|
387
424
|
end
|
388
425
|
|
389
426
|
def complete_experiment(experiment)
|
390
|
-
unless experiment['
|
391
|
-
experiment['variations'] = experiment['
|
427
|
+
unless experiment['variations'].nil?
|
428
|
+
experiment['variations'] = experiment['variations'].map { |variationId| obtain_variation(variationId) }
|
392
429
|
end
|
393
430
|
unless experiment['targetingSegmentId'].nil?
|
394
431
|
experiment['targetingSegment'] = Kameleoon::Targeting::Segment.new(obtain_segment(experiment['targetingSegmentId']))
|
@@ -396,6 +433,44 @@ module Kameleoon
|
|
396
433
|
experiment
|
397
434
|
end
|
398
435
|
|
436
|
+
# fetching segment for both types: experiments and feature_flags (campaigns)
|
437
|
+
def complete_campaign_graphql(campaign)
|
438
|
+
campaign['id'] = campaign['id'].to_i
|
439
|
+
campaign['status'] = campaign['status']
|
440
|
+
unless campaign['deviations'].nil?
|
441
|
+
campaign['deviations'] = Hash[*campaign['deviations'].map { |it| [ it['variationId'] == '0' ? 'origin' : it['variationId'], it['value'] ] }.flatten]
|
442
|
+
end
|
443
|
+
unless campaign['respoolTime'].nil?
|
444
|
+
campaign['respoolTime'] = Hash[*campaign['respoolTime'].map { |it| [ it['variationId'] == '0' ? 'origin' : it['variationId'], it['value'] ] }.flatten]
|
445
|
+
end
|
446
|
+
unless campaign['variations'].nil?
|
447
|
+
campaign['variations'] = campaign['variations'].map { |it| {'id' => it['id'].to_i, 'customJson' => it['customJson']} }
|
448
|
+
end
|
449
|
+
unless campaign['segment'].nil?
|
450
|
+
campaign['targetingSegment'] = Kameleoon::Targeting::Segment.new((campaign['segment']))
|
451
|
+
end
|
452
|
+
campaign
|
453
|
+
end
|
454
|
+
|
455
|
+
def complete_experiment(experiment)
|
456
|
+
unless experiment['variations'].nil?
|
457
|
+
experiment['variations'] = experiment['variations'].map { |variationId| obtain_variation(variationId) }
|
458
|
+
end
|
459
|
+
unless experiment['targetingSegmentId'].nil?
|
460
|
+
experiment['targetingSegment'] = Kameleoon::Targeting::Segment.new(obtain_segment(experiment['targetingSegmentId']))
|
461
|
+
end
|
462
|
+
experiment
|
463
|
+
end
|
464
|
+
|
465
|
+
def obtain_experiments_graphql(site_code, per_page = -1)
|
466
|
+
log "Fetching experiments GraphQL"
|
467
|
+
experiments = fetch_all_graphql("v1/graphql?perPage=#{per_page}", Kameleoon::Query.query_experiments(site_code)).map { |it| JSON.parse(it.response)['data']['experiments']['edges'] }.flatten.map do |experiment|
|
468
|
+
complete_campaign_graphql(experiment['node'])
|
469
|
+
end
|
470
|
+
log "Experiment are fetched: " + experiments.inspect
|
471
|
+
experiments
|
472
|
+
end
|
473
|
+
|
399
474
|
def obtain_tests(site_id, per_page = -1)
|
400
475
|
log "Fetching experiments"
|
401
476
|
query_values = { 'perPage' => per_page }
|
@@ -411,12 +486,21 @@ module Kameleoon
|
|
411
486
|
experiments
|
412
487
|
end
|
413
488
|
|
489
|
+
def obtain_feature_flags_graphql(site_id, environment = @environment, per_page = -1)
|
490
|
+
log "Fetching feature flags GraphQL"
|
491
|
+
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|
|
492
|
+
complete_campaign_graphql(feature_flag['node'])
|
493
|
+
end
|
494
|
+
log "Feature flags are fetched: " + feature_flags.inspect
|
495
|
+
feature_flags
|
496
|
+
end
|
497
|
+
|
414
498
|
def obtain_feature_flags(site_id, per_page = -1)
|
415
499
|
log "Fetching feature flags"
|
416
500
|
query_values = { 'perPage' => per_page }
|
417
501
|
filters = [
|
418
502
|
hash_filter('siteId', 'EQUAL', [site_id]),
|
419
|
-
hash_filter('status', '
|
503
|
+
hash_filter('status', 'IN', ['ACTIVE', 'PAUSED'])
|
420
504
|
]
|
421
505
|
feature_flags = fetch_all('feature-flags', query_values, filters).map { |it| JSON.parse(it.response) }.flatten.map do |ff|
|
422
506
|
complete_experiment(ff)
|
@@ -425,6 +509,19 @@ module Kameleoon
|
|
425
509
|
feature_flags
|
426
510
|
end
|
427
511
|
|
512
|
+
def fetch_all_graphql(path, query_graphql = {})
|
513
|
+
results = []
|
514
|
+
current_page = 1
|
515
|
+
loop do
|
516
|
+
http = fetch_one_graphql(path, query_graphql)
|
517
|
+
break if http == false
|
518
|
+
results.push(http)
|
519
|
+
break if http.response_header["X-Pagination-Page-Count"].to_i <= current_page
|
520
|
+
current_page += 1
|
521
|
+
end
|
522
|
+
results
|
523
|
+
end
|
524
|
+
|
428
525
|
def fetch_all(path, query_values = {}, filters = [])
|
429
526
|
results = []
|
430
527
|
current_page = 1
|
@@ -439,6 +536,15 @@ module Kameleoon
|
|
439
536
|
results
|
440
537
|
end
|
441
538
|
|
539
|
+
def fetch_one_graphql(path, query_graphql = {})
|
540
|
+
request = EM::Synchrony.sync post({ :path => path, :body => query_graphql, :head => hash_headers })
|
541
|
+
unless is_successful(request)
|
542
|
+
log "Failed to fetch" + request.inspect
|
543
|
+
return false
|
544
|
+
end
|
545
|
+
request
|
546
|
+
end
|
547
|
+
|
442
548
|
def fetch_one(path, query_values = {}, filters = [])
|
443
549
|
unless filters.empty?
|
444
550
|
query_values['filter'] = filters.to_json
|
@@ -481,6 +587,8 @@ module Kameleoon
|
|
481
587
|
feature_flag = @feature_flags.select { |ff| ff['identificationKey'] == feature_key}.first
|
482
588
|
elsif feature_key.is_a?(Integer)
|
483
589
|
feature_flag = @feature_flags.select { |ff| ff['id'].to_i == feature_key}.first
|
590
|
+
print "\nPassing `feature_key` with type of `int` to `activate_feature` or `obtain_feature_variable` "\
|
591
|
+
"is deprecated, it will be removed in next releases. This is necessary to support multi-environment feature\n"
|
484
592
|
else
|
485
593
|
raise TypeError.new("Feature key should be a String or an Integer.")
|
486
594
|
end
|
@@ -490,6 +598,20 @@ module Kameleoon
|
|
490
598
|
feature_flag
|
491
599
|
end
|
492
600
|
|
601
|
+
def is_feature_flag_scheduled(feature_flag, date)
|
602
|
+
current_status = feature_flag['status'] == STATUS_ACTIVE
|
603
|
+
if feature_flag['featureStatus'] == FEATURE_STATUS_DEACTIVATED || feature_flag['schedules'].empty?
|
604
|
+
return current_status
|
605
|
+
end
|
606
|
+
feature_flag['schedules'].each do |schedule|
|
607
|
+
if (schedule['dateStart'].nil? || Time.parse(schedule['dateStart']).to_i < date) &&
|
608
|
+
(schedule['dateEnd'].nil? || Time.parse(schedule['dateEnd']).to_i > date)
|
609
|
+
return true
|
610
|
+
end
|
611
|
+
end
|
612
|
+
false
|
613
|
+
end
|
614
|
+
|
493
615
|
def track_experiment(visitor_code, experiment_id, variation_id = nil, none_variation = false)
|
494
616
|
data_not_sent = data_not_sent(visitor_code)
|
495
617
|
options = {
|
@@ -498,6 +620,7 @@ module Kameleoon
|
|
498
620
|
:head => { "Content-Type" => "text/plain"}
|
499
621
|
}
|
500
622
|
trial = 0
|
623
|
+
success = false
|
501
624
|
log "Start post tracking experiment: " + data_not_sent.inspect
|
502
625
|
Thread.new do
|
503
626
|
EM.synchrony do
|
@@ -507,12 +630,18 @@ module Kameleoon
|
|
507
630
|
if is_successful(request)
|
508
631
|
(data_not_sent.values[0] || []).each { |it| it.sent = true }
|
509
632
|
EM.stop
|
633
|
+
success = true
|
634
|
+
break
|
510
635
|
end
|
511
636
|
trial += 1
|
512
637
|
end
|
513
638
|
EM.stop
|
514
639
|
end
|
515
|
-
|
640
|
+
if success
|
641
|
+
log "Post to experiment tracking is done after " + (trial + 1).to_s + " trials"
|
642
|
+
elsif
|
643
|
+
log "Post to experiment tracking is failed after " + trial.to_s + " trials"
|
644
|
+
end
|
516
645
|
Thread.exit
|
517
646
|
end
|
518
647
|
end
|
@@ -551,6 +680,12 @@ module Kameleoon
|
|
551
680
|
end
|
552
681
|
end
|
553
682
|
|
683
|
+
def check_site_code_enable(exp_or_ff)
|
684
|
+
unless exp_or_ff['site'].nil? || exp_or_ff['site']['isKameleoonEnabled']
|
685
|
+
raise Exception::SiteCodeDisabled.new(site_code)
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
554
689
|
def data_not_sent(visitor_code = nil)
|
555
690
|
if visitor_code.nil?
|
556
691
|
@data.select {|key, values| values.any? {|data| !data.sent}}
|
@@ -565,4 +700,4 @@ module Kameleoon
|
|
565
700
|
end
|
566
701
|
end
|
567
702
|
end
|
568
|
-
end
|
703
|
+
end
|
data/lib/kameleoon/cookie.rb
CHANGED
@@ -26,10 +26,18 @@ module Kameleoon
|
|
26
26
|
identifier = visitor_code.to_s
|
27
27
|
identifier += container_id.to_s
|
28
28
|
if !respool_times.nil? && !respool_times.empty?
|
29
|
-
identifier += respool_times.values.
|
29
|
+
identifier += respool_times.sort.to_h.values.join.to_s
|
30
30
|
end
|
31
31
|
(Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal("2") ** BigDecimal("256"))).round(16)
|
32
32
|
end
|
33
|
+
|
34
|
+
def check_visitor_code(visitor_code)
|
35
|
+
if visitor_code.nil?
|
36
|
+
check_default_visitor_code('')
|
37
|
+
elsif
|
38
|
+
check_default_visitor_code(visitor_code)
|
39
|
+
end
|
40
|
+
end
|
33
41
|
|
34
42
|
private
|
35
43
|
|
@@ -43,6 +51,7 @@ module Kameleoon
|
|
43
51
|
if default_visitor_code.nil?
|
44
52
|
return
|
45
53
|
end
|
54
|
+
default_visitor_code = default_visitor_code.to_s
|
46
55
|
if default_visitor_code.length == 0
|
47
56
|
raise Kameleoon::Exception::VisitorCodeNotValid.new("Empty visitor Code")
|
48
57
|
end
|
data/lib/kameleoon/data.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module Kameleoon
|
3
2
|
NONCE_LENGTH = 16
|
4
3
|
|
@@ -29,6 +28,17 @@ module Kameleoon
|
|
29
28
|
def obtain_full_post_text_line
|
30
29
|
raise KameleoonError.new("ToDo: implement this method.")
|
31
30
|
end
|
31
|
+
|
32
|
+
def encode(url)
|
33
|
+
encoded_url = CGI.escape(url)
|
34
|
+
encoded_url.gsub! "%27", "'"
|
35
|
+
encoded_url.gsub! "%21", "!"
|
36
|
+
encoded_url.gsub! "+", "%20"
|
37
|
+
encoded_url.gsub! "%2A", "*"
|
38
|
+
encoded_url.gsub! "%28", "("
|
39
|
+
encoded_url.gsub! "%29", ")"
|
40
|
+
encoded_url
|
41
|
+
end
|
32
42
|
end
|
33
43
|
|
34
44
|
class CustomData < Data
|
@@ -63,7 +73,7 @@ module Kameleoon
|
|
63
73
|
def obtain_full_post_text_line
|
64
74
|
to_encode = "[[\"" + @value.to_s.gsub("\"", "\\\"") + "\",1]]"
|
65
75
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
66
|
-
"eventType=customData&index=" + @id.to_s + "&valueToCount=" +
|
76
|
+
"eventType=customData&index=" + @id.to_s + "&valueToCount=" + encode(to_encode) + "&overwrite=true&nonce=" + nonce
|
67
77
|
end
|
68
78
|
end
|
69
79
|
|
@@ -101,9 +111,9 @@ module Kameleoon
|
|
101
111
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
102
112
|
referrer_text = ""
|
103
113
|
unless @referrer.nil?
|
104
|
-
referrer_text = "&referrers=[" + @referrer + "]"
|
114
|
+
referrer_text = "&referrers=[" + @referrer.to_s + "]"
|
105
115
|
end
|
106
|
-
"eventType=page&href=" + @url + "&title=" + @title + "&keyPages=[]" + referrer_text + "&nonce=" + nonce
|
116
|
+
"eventType=page&href=" + encode(@url) + "&title=" + @title + "&keyPages=[]" + referrer_text + "&nonce=" + nonce
|
107
117
|
end
|
108
118
|
end
|
109
119
|
|
data/lib/kameleoon/exceptions.rb
CHANGED
@@ -10,7 +10,7 @@ module Kameleoon
|
|
10
10
|
super(value.to_s + " not found.")
|
11
11
|
end
|
12
12
|
end
|
13
|
-
class
|
13
|
+
class VariationConfigurationNotFound < NotFound
|
14
14
|
def initialize(id = "")
|
15
15
|
super("Variation " + id.to_s)
|
16
16
|
end
|
@@ -50,5 +50,10 @@ module Kameleoon
|
|
50
50
|
super("Visitor code not valid: " + message)
|
51
51
|
end
|
52
52
|
end
|
53
|
+
class SiteCodeDisabled < KameleoonError
|
54
|
+
def initialize(message = "")
|
55
|
+
super("Site with siteCode '" + message + "' is disabled")
|
56
|
+
end
|
57
|
+
end
|
53
58
|
end
|
54
59
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Kameleoon
|
2
|
+
module Query
|
3
|
+
|
4
|
+
def self.query_experiments(site_code)
|
5
|
+
'{
|
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 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
|
+
"variables": {
|
9
|
+
"filter": {
|
10
|
+
"and": [{
|
11
|
+
"condition": {
|
12
|
+
"field": "status",
|
13
|
+
"operator": "IN",
|
14
|
+
"parameters": ["ACTIVE", "DEVIATED", "USED_AS_PERSONALIZATION"]
|
15
|
+
}
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"condition": {
|
19
|
+
"field": "type",
|
20
|
+
"operator": "IN",
|
21
|
+
"parameters": ["SERVER_SIDE", "HYBRID"]
|
22
|
+
}
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"condition": {
|
26
|
+
"field": "siteCode",
|
27
|
+
"operator": "IN",
|
28
|
+
"parameters": ["' + site_code + '"]
|
29
|
+
}
|
30
|
+
}]
|
31
|
+
},
|
32
|
+
"sort": [{
|
33
|
+
"field": "id",
|
34
|
+
"direction": "ASC"
|
35
|
+
}]
|
36
|
+
}
|
37
|
+
}'
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.query_feature_flags(site_code, environment)
|
41
|
+
'{
|
42
|
+
"operationName": "getFeatureFlags",
|
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 } }",
|
44
|
+
"variables": {
|
45
|
+
"filter": {
|
46
|
+
"and": [{
|
47
|
+
"condition": {
|
48
|
+
"field": "featureStatus",
|
49
|
+
"operator": "IN",
|
50
|
+
"parameters": ["ACTIVATED", "SCHEDULED", "DEACTIVATED"]
|
51
|
+
}
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"condition": {
|
55
|
+
"field": "siteCode",
|
56
|
+
"operator": "IN",
|
57
|
+
"parameters": ["' + site_code + '"]
|
58
|
+
}
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"condition": {
|
62
|
+
"field": "environment.key",
|
63
|
+
"operator": "IN",
|
64
|
+
"parameters": ["' + environment + '"]
|
65
|
+
}
|
66
|
+
}]
|
67
|
+
},
|
68
|
+
"sort": [{
|
69
|
+
"field": "id",
|
70
|
+
"direction": "ASC"
|
71
|
+
}]
|
72
|
+
}
|
73
|
+
}'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'kameleoon/targeting/condition'
|
2
2
|
require 'kameleoon/exceptions'
|
3
|
-
require 'kameleoon/targeting/models'
|
4
3
|
|
5
4
|
module Kameleoon
|
6
5
|
#@api private
|
@@ -20,22 +19,22 @@ module Kameleoon
|
|
20
19
|
end
|
21
20
|
@operator = json_condition['valueMatchType']
|
22
21
|
|
23
|
-
if json_condition['value'].nil?
|
24
|
-
|
25
|
-
end
|
22
|
+
# if json_condition['value'].nil?
|
23
|
+
# raise Exception::NotFoundError.new('value')
|
24
|
+
# end
|
26
25
|
@value = json_condition['value']
|
27
26
|
|
28
27
|
@type = ConditionType::CUSTOM_DATUM
|
29
28
|
|
30
|
-
if json_condition['include'].nil?
|
31
|
-
raise Exception::NotFoundError.new('include')
|
29
|
+
if json_condition['include'].nil? && json_condition['isInclude'].nil?
|
30
|
+
raise Exception::NotFoundError.new('include / isInclude missed')
|
32
31
|
end
|
33
|
-
@include = json_condition['include']
|
32
|
+
@include = json_condition['include'] || json_condition['isInclude']
|
34
33
|
end
|
35
34
|
|
36
35
|
def check(datas)
|
37
36
|
is_targeted = false
|
38
|
-
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
|
39
38
|
if custom_data.nil?
|
40
39
|
is_targeted = (@operator == Operator::UNDEFINED.to_s)
|
41
40
|
else
|
@@ -65,11 +64,11 @@ module Kameleoon
|
|
65
64
|
is_targeted = true
|
66
65
|
end
|
67
66
|
when Operator::IS_TRUE.to_s
|
68
|
-
if custom_data.value == true
|
67
|
+
if custom_data.value == 'true'
|
69
68
|
is_targeted = true
|
70
69
|
end
|
71
70
|
when Operator::IS_FALSE.to_s
|
72
|
-
if custom_data.value == false
|
71
|
+
if custom_data.value == 'false'
|
73
72
|
is_targeted = true
|
74
73
|
end
|
75
74
|
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.0.
|
4
|
+
version: 1.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Kameleoon
|
7
|
+
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '3.7'
|
55
55
|
description: Kameleoon Client Ruby Software Development Kit
|
56
56
|
email:
|
57
|
-
-
|
57
|
+
- sdk@kameleoon.com
|
58
58
|
executables: []
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- lib/kameleoon/data.rb
|
67
67
|
- lib/kameleoon/exceptions.rb
|
68
68
|
- lib/kameleoon/factory.rb
|
69
|
+
- lib/kameleoon/query_graphql.rb
|
69
70
|
- lib/kameleoon/request.rb
|
70
71
|
- lib/kameleoon/targeting/condition.rb
|
71
72
|
- lib/kameleoon/targeting/condition_factory.rb
|