kameleoon-client-ruby 1.0.5 → 1.0.9
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 +141 -21
- data/lib/kameleoon/cookie.rb +23 -7
- data/lib/kameleoon/data.rb +14 -4
- data/lib/kameleoon/exceptions.rb +6 -1
- data/lib/kameleoon/query_graphql.rb +66 -0
- data/lib/kameleoon/targeting/condition_factory.rb +0 -1
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +8 -9
- 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: 887e6709dff33fa410a8e34a32577d8ad1ffe56fa52cadd24bd1734c68d2e46b
|
4
|
+
data.tar.gz: e42d4d944e8c1549eae488f74e7b5c8891bf41f35dcce02133bfc68cee969463
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cf8ee8561091145b530082e2ce86614b41f9c63c70b644518c5a9d3738e659084526003ad298cdf320da0570cc7d55b551b3b80e680877a1c4c0bd984bc1b9f
|
7
|
+
data.tar.gz: 4c9e4873467d1f2578be20f8246a47ad3810401251fb9e3dd34d3baad073184c6083fa0a35893c95e308a3482ebb3b42d58e1fd8e82c9400fa554ebbd1e16d63
|
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
@@ -2,11 +2,13 @@ require 'kameleoon/targeting/models'
|
|
2
2
|
require 'kameleoon/request'
|
3
3
|
require 'kameleoon/exceptions'
|
4
4
|
require 'kameleoon/cookie'
|
5
|
+
require 'kameleoon/query_graphql'
|
5
6
|
require 'rufus/scheduler'
|
6
7
|
require 'yaml'
|
7
8
|
require 'json'
|
8
9
|
require 'em-synchrony'
|
9
10
|
require 'objspace'
|
11
|
+
require 'time'
|
10
12
|
|
11
13
|
module Kameleoon
|
12
14
|
##
|
@@ -83,8 +85,10 @@ module Kameleoon
|
|
83
85
|
# @raise [Kameleoon::Exception::ExperimentConfigurationNotFound] Raise when experiment configuration is not found
|
84
86
|
# @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
87
|
# @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
|
88
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
86
89
|
#
|
87
90
|
def trigger_experiment(visitor_code, experiment_id, timeout = @default_timeout)
|
91
|
+
check_visitor_code(visitor_code)
|
88
92
|
experiment = @experiments.find { |experiment| experiment['id'].to_s == experiment_id.to_s }
|
89
93
|
if experiment.nil?
|
90
94
|
raise Exception::ExperimentConfigurationNotFound.new(experiment_id)
|
@@ -143,7 +147,10 @@ module Kameleoon
|
|
143
147
|
# @param [String] visitor_code Visitor code
|
144
148
|
# @param [...Data] data Data to associate with the visitor code
|
145
149
|
#
|
150
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
151
|
+
#
|
146
152
|
def add_data(visitor_code, *args)
|
153
|
+
check_visitor_code(visitor_code)
|
147
154
|
while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20) do
|
148
155
|
@data.shift
|
149
156
|
end
|
@@ -167,7 +174,10 @@ module Kameleoon
|
|
167
174
|
# @param [Integer] goal_id Id of the goal
|
168
175
|
# @param [Float] revenue Optional - Revenue of the conversion.
|
169
176
|
#
|
177
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
178
|
+
#
|
170
179
|
def track_conversion(visitor_code, goal_id, revenue = 0.0)
|
180
|
+
check_visitor_code(visitor_code)
|
171
181
|
add_data(visitor_code, Conversion.new(goal_id, revenue))
|
172
182
|
flush(visitor_code)
|
173
183
|
end
|
@@ -181,7 +191,10 @@ module Kameleoon
|
|
181
191
|
#
|
182
192
|
# @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
|
183
193
|
#
|
194
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is not null and empty or longer than 255 chars
|
195
|
+
#
|
184
196
|
def flush(visitor_code = nil)
|
197
|
+
check_visitor_code(visitor_code) unless visitor_code.nil?
|
185
198
|
track_data(visitor_code)
|
186
199
|
end
|
187
200
|
|
@@ -203,9 +216,9 @@ module Kameleoon
|
|
203
216
|
def obtain_variation_associated_data(variation_id)
|
204
217
|
variation = @experiments.map { |experiment| experiment['variations'] }.flatten.select { |variation| variation['id'].to_i == variation_id.to_i }.first
|
205
218
|
if variation.nil?
|
206
|
-
raise Exception::
|
219
|
+
raise Exception::VariationConfigurationNotFound.new(variation_id)
|
207
220
|
else
|
208
|
-
variation['customJson']
|
221
|
+
JSON.parse(variation['customJson'])
|
209
222
|
end
|
210
223
|
end
|
211
224
|
|
@@ -221,8 +234,10 @@ module Kameleoon
|
|
221
234
|
#
|
222
235
|
# @raise [Kameleoon::Exception::FeatureConfigurationNotFound]
|
223
236
|
# @raise [Kameleoon::Exception::NotTargeted]
|
237
|
+
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
224
238
|
#
|
225
239
|
def activate_feature(visitor_code, feature_key, timeout = @default_timeout)
|
240
|
+
check_visitor_code(visitor_code)
|
226
241
|
feature_flag = get_feature_flag(feature_key)
|
227
242
|
id = feature_flag['id']
|
228
243
|
if @blocking
|
@@ -248,17 +263,21 @@ module Kameleoon
|
|
248
263
|
|
249
264
|
else
|
250
265
|
visitor_data = @data.select { |key, value| key.to_s == visitor_code }.values.flatten! || []
|
251
|
-
|
266
|
+
unless feature_flag['targetingSegment'].nil? || feature_flag['targetingSegment'].check_tree(visitor_data)
|
267
|
+
raise Exception::NotTargeted.new(visitor_code)
|
268
|
+
end
|
269
|
+
|
270
|
+
if is_feature_flag_scheduled(feature_flag, Time.now.to_i)
|
252
271
|
threshold = obtain_hash_double(visitor_code, {}, id)
|
253
272
|
if threshold <= feature_flag['expositionRate']
|
254
|
-
track_experiment(visitor_code, id, feature_flag["
|
255
|
-
|
273
|
+
track_experiment(visitor_code, id, feature_flag["variations"].first['id'])
|
274
|
+
true
|
256
275
|
else
|
257
276
|
track_experiment(visitor_code, id, REFERENCE, true)
|
258
|
-
|
277
|
+
false
|
259
278
|
end
|
260
279
|
else
|
261
|
-
|
280
|
+
false
|
262
281
|
end
|
263
282
|
end
|
264
283
|
end
|
@@ -276,19 +295,19 @@ module Kameleoon
|
|
276
295
|
#
|
277
296
|
def obtain_feature_variable(feature_key, variable_key)
|
278
297
|
feature_flag = get_feature_flag(feature_key)
|
279
|
-
custom_json = feature_flag["variations"].first['customJson'][variable_key.to_s]
|
298
|
+
custom_json = JSON.parse(feature_flag["variations"].first['customJson'])[variable_key.to_s]
|
280
299
|
if custom_json.nil?
|
281
300
|
raise Exception::FeatureVariableNotFound.new("Feature variable not found")
|
282
301
|
end
|
283
302
|
case custom_json['type']
|
284
303
|
when "Boolean"
|
285
|
-
return
|
304
|
+
return custom_json['value'].downcase == "true"
|
286
305
|
when "String"
|
287
|
-
return custom_json['value'].to_s
|
288
|
-
when "Number"
|
289
|
-
return custom_json['value'].to_f
|
290
|
-
when "Json"
|
291
306
|
return custom_json['value']
|
307
|
+
when "Number"
|
308
|
+
return custom_json['value'].to_i
|
309
|
+
when "JSON"
|
310
|
+
return JSON.parse(custom_json['value'])
|
292
311
|
else
|
293
312
|
raise TypeError.new("Unknown type for feature variable")
|
294
313
|
end
|
@@ -297,6 +316,8 @@ module Kameleoon
|
|
297
316
|
private
|
298
317
|
API_SSX_URL = "https://api-ssx.kameleoon.com"
|
299
318
|
REFERENCE = 0
|
319
|
+
STATUS_ACTIVE = "ACTIVE"
|
320
|
+
FEATURE_STATUS_DEACTIVATED = "DEACTIVATED"
|
300
321
|
attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
|
301
322
|
:blocking, :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
|
302
323
|
|
@@ -304,11 +325,20 @@ module Kameleoon
|
|
304
325
|
@scheduler = Rufus::Scheduler.singleton
|
305
326
|
@scheduler.every @interval do
|
306
327
|
log("Scheduled job to fetch configuration is starting.")
|
307
|
-
|
328
|
+
fetch_configuration_job_graphql
|
308
329
|
end
|
309
330
|
@scheduler.schedule '0s' do
|
310
331
|
log("Start-up, fetching is starting")
|
311
|
-
|
332
|
+
fetch_configuration_job_graphql
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def fetch_configuration_job_graphql
|
337
|
+
EM.synchrony do
|
338
|
+
obtain_access_token
|
339
|
+
@experiments = obtain_experiments_graphql(@site_code) || @experiments
|
340
|
+
@feature_flags = obtain_feature_flags_graphql(@site_code) || @feature_flags
|
341
|
+
EM.stop
|
312
342
|
end
|
313
343
|
end
|
314
344
|
|
@@ -387,8 +417,37 @@ module Kameleoon
|
|
387
417
|
end
|
388
418
|
|
389
419
|
def complete_experiment(experiment)
|
390
|
-
unless experiment['
|
391
|
-
experiment['variations'] = experiment['
|
420
|
+
unless experiment['variations'].nil?
|
421
|
+
experiment['variations'] = experiment['variations'].map { |variationId| obtain_variation(variationId) }
|
422
|
+
end
|
423
|
+
unless experiment['targetingSegmentId'].nil?
|
424
|
+
experiment['targetingSegment'] = Kameleoon::Targeting::Segment.new(obtain_segment(experiment['targetingSegmentId']))
|
425
|
+
end
|
426
|
+
experiment
|
427
|
+
end
|
428
|
+
|
429
|
+
# fetching segment for both types: experiments and feature_flags (campaigns)
|
430
|
+
def complete_campaign_graphql(campaign)
|
431
|
+
campaign['id'] = campaign['id'].to_i
|
432
|
+
campaign['status'] = campaign['status']
|
433
|
+
unless campaign['deviations'].nil?
|
434
|
+
campaign['deviations'] = Hash[*campaign['deviations'].map { |it| [ it['variationId'] == '0' ? 'origin' : it['variationId'], it['value'] ] }.flatten]
|
435
|
+
end
|
436
|
+
unless campaign['respoolTime'].nil?
|
437
|
+
campaign['respoolTime'] = Hash[*campaign['respoolTime'].map { |it| [ it['variationId'] == '0' ? 'origin' : it['variationId'], it['value'] ] }.flatten]
|
438
|
+
end
|
439
|
+
unless campaign['variations'].nil?
|
440
|
+
campaign['variations'] = campaign['variations'].map { |it| {'id' => it['id'].to_i, 'customJson' => it['customJson']} }
|
441
|
+
end
|
442
|
+
unless campaign['segment'].nil?
|
443
|
+
campaign['targetingSegment'] = Kameleoon::Targeting::Segment.new((campaign['segment']))
|
444
|
+
end
|
445
|
+
campaign
|
446
|
+
end
|
447
|
+
|
448
|
+
def complete_experiment(experiment)
|
449
|
+
unless experiment['variations'].nil?
|
450
|
+
experiment['variations'] = experiment['variations'].map { |variationId| obtain_variation(variationId) }
|
392
451
|
end
|
393
452
|
unless experiment['targetingSegmentId'].nil?
|
394
453
|
experiment['targetingSegment'] = Kameleoon::Targeting::Segment.new(obtain_segment(experiment['targetingSegmentId']))
|
@@ -396,12 +455,21 @@ module Kameleoon
|
|
396
455
|
experiment
|
397
456
|
end
|
398
457
|
|
458
|
+
def obtain_experiments_graphql(site_code, per_page = -1)
|
459
|
+
log "Fetching experiments GraphQL"
|
460
|
+
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|
|
461
|
+
complete_campaign_graphql(experiment['node'])
|
462
|
+
end
|
463
|
+
log "Experiment are fetched: " + experiments.inspect
|
464
|
+
experiments
|
465
|
+
end
|
466
|
+
|
399
467
|
def obtain_tests(site_id, per_page = -1)
|
400
468
|
log "Fetching experiments"
|
401
469
|
query_values = { 'perPage' => per_page }
|
402
470
|
filters = [
|
403
471
|
hash_filter('siteId', 'EQUAL', [site_id]),
|
404
|
-
hash_filter('status', '
|
472
|
+
hash_filter('status', 'IN', ['ACTIVE', 'DEVIATED']),
|
405
473
|
hash_filter('type', 'IN', ['SERVER_SIDE', 'HYBRID'])
|
406
474
|
]
|
407
475
|
experiments = fetch_all('experiments', query_values, filters).map { |it| JSON.parse(it.response) }.flatten.map do |test|
|
@@ -411,12 +479,21 @@ module Kameleoon
|
|
411
479
|
experiments
|
412
480
|
end
|
413
481
|
|
482
|
+
def obtain_feature_flags_graphql(site_id, per_page = -1)
|
483
|
+
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|
|
485
|
+
complete_campaign_graphql(feature_flag['node'])
|
486
|
+
end
|
487
|
+
log "Feature flags are fetched: " + feature_flags.inspect
|
488
|
+
feature_flags
|
489
|
+
end
|
490
|
+
|
414
491
|
def obtain_feature_flags(site_id, per_page = -1)
|
415
492
|
log "Fetching feature flags"
|
416
493
|
query_values = { 'perPage' => per_page }
|
417
494
|
filters = [
|
418
495
|
hash_filter('siteId', 'EQUAL', [site_id]),
|
419
|
-
hash_filter('status', '
|
496
|
+
hash_filter('status', 'IN', ['ACTIVE', 'PAUSED'])
|
420
497
|
]
|
421
498
|
feature_flags = fetch_all('feature-flags', query_values, filters).map { |it| JSON.parse(it.response) }.flatten.map do |ff|
|
422
499
|
complete_experiment(ff)
|
@@ -425,6 +502,19 @@ module Kameleoon
|
|
425
502
|
feature_flags
|
426
503
|
end
|
427
504
|
|
505
|
+
def fetch_all_graphql(path, query_graphql = {})
|
506
|
+
results = []
|
507
|
+
current_page = 1
|
508
|
+
loop do
|
509
|
+
http = fetch_one_graphql(path, query_graphql)
|
510
|
+
break if http == false
|
511
|
+
results.push(http)
|
512
|
+
break if http.response_header["X-Pagination-Page-Count"].to_i <= current_page
|
513
|
+
current_page += 1
|
514
|
+
end
|
515
|
+
results
|
516
|
+
end
|
517
|
+
|
428
518
|
def fetch_all(path, query_values = {}, filters = [])
|
429
519
|
results = []
|
430
520
|
current_page = 1
|
@@ -439,6 +529,15 @@ module Kameleoon
|
|
439
529
|
results
|
440
530
|
end
|
441
531
|
|
532
|
+
def fetch_one_graphql(path, query_graphql = {})
|
533
|
+
request = EM::Synchrony.sync post({ :path => path, :body => query_graphql, :head => hash_headers })
|
534
|
+
unless is_successful(request)
|
535
|
+
log "Failed to fetch" + request.inspect
|
536
|
+
return false
|
537
|
+
end
|
538
|
+
request
|
539
|
+
end
|
540
|
+
|
442
541
|
def fetch_one(path, query_values = {}, filters = [])
|
443
542
|
unless filters.empty?
|
444
543
|
query_values['filter'] = filters.to_json
|
@@ -490,6 +589,20 @@ module Kameleoon
|
|
490
589
|
feature_flag
|
491
590
|
end
|
492
591
|
|
592
|
+
def is_feature_flag_scheduled(feature_flag, date)
|
593
|
+
current_status = feature_flag['status'] == STATUS_ACTIVE
|
594
|
+
if feature_flag['featureStatus'] == FEATURE_STATUS_DEACTIVATED || feature_flag['schedules'].empty?
|
595
|
+
return current_status
|
596
|
+
end
|
597
|
+
feature_flag['schedules'].each do |schedule|
|
598
|
+
if (schedule['dateStart'].nil? || Time.parse(schedule['dateStart']).to_i < date) &&
|
599
|
+
(schedule['dateEnd'].nil? || Time.parse(schedule['dateEnd']).to_i > date)
|
600
|
+
return true
|
601
|
+
end
|
602
|
+
end
|
603
|
+
false
|
604
|
+
end
|
605
|
+
|
493
606
|
def track_experiment(visitor_code, experiment_id, variation_id = nil, none_variation = false)
|
494
607
|
data_not_sent = data_not_sent(visitor_code)
|
495
608
|
options = {
|
@@ -498,6 +611,7 @@ module Kameleoon
|
|
498
611
|
:head => { "Content-Type" => "text/plain"}
|
499
612
|
}
|
500
613
|
trial = 0
|
614
|
+
success = false
|
501
615
|
log "Start post tracking experiment: " + data_not_sent.inspect
|
502
616
|
Thread.new do
|
503
617
|
EM.synchrony do
|
@@ -507,12 +621,18 @@ module Kameleoon
|
|
507
621
|
if is_successful(request)
|
508
622
|
(data_not_sent.values[0] || []).each { |it| it.sent = true }
|
509
623
|
EM.stop
|
624
|
+
success = true
|
625
|
+
break
|
510
626
|
end
|
511
627
|
trial += 1
|
512
628
|
end
|
513
629
|
EM.stop
|
514
630
|
end
|
515
|
-
|
631
|
+
if success
|
632
|
+
log "Post to experiment tracking is done after " + (trial + 1).to_s + " trials"
|
633
|
+
elsif
|
634
|
+
log "Post to experiment tracking is failed after " + trial.to_s + " trials"
|
635
|
+
end
|
516
636
|
Thread.exit
|
517
637
|
end
|
518
638
|
end
|
@@ -565,4 +685,4 @@ module Kameleoon
|
|
565
685
|
end
|
566
686
|
end
|
567
687
|
end
|
568
|
-
end
|
688
|
+
end
|
data/lib/kameleoon/cookie.rb
CHANGED
@@ -3,6 +3,7 @@ require 'em-http'
|
|
3
3
|
require 'eventmachine'
|
4
4
|
require 'bigdecimal'
|
5
5
|
require 'kameleoon/utils'
|
6
|
+
require 'kameleoon/exceptions'
|
6
7
|
|
7
8
|
module Kameleoon
|
8
9
|
# @api private
|
@@ -25,24 +26,39 @@ module Kameleoon
|
|
25
26
|
identifier = visitor_code.to_s
|
26
27
|
identifier += container_id.to_s
|
27
28
|
if !respool_times.nil? && !respool_times.empty?
|
28
|
-
identifier += respool_times.values.
|
29
|
+
identifier += respool_times.sort.to_h.values.join.to_s
|
29
30
|
end
|
30
31
|
(Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal("2") ** BigDecimal("256"))).round(16)
|
31
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
|
32
41
|
|
33
42
|
private
|
34
43
|
|
35
44
|
KAMELEOON_KEY_JS_COOKIE = "_js_"
|
45
|
+
KAMELEOON_VISITOR_CODE_LENGTH = 255
|
36
46
|
KAMELEOON_COOKIE_VALUE_LENGTH = 16
|
37
|
-
KAMELEOON_VISITOR_CODE_LENGTH = 100
|
38
47
|
KAMELEOON_COOKIE_NAME = 'kameleoonVisitorCode'
|
39
48
|
EXPIRE_DAYS = 380
|
40
49
|
|
41
50
|
def check_default_visitor_code(default_visitor_code)
|
42
51
|
if default_visitor_code.nil?
|
43
|
-
return
|
52
|
+
return
|
53
|
+
end
|
54
|
+
default_visitor_code = default_visitor_code.to_s
|
55
|
+
if default_visitor_code.length == 0
|
56
|
+
raise Kameleoon::Exception::VisitorCodeNotValid.new("Empty visitor Code")
|
57
|
+
end
|
58
|
+
if default_visitor_code.length > KAMELEOON_VISITOR_CODE_LENGTH
|
59
|
+
raise Kameleoon::Exception::VisitorCodeNotValid.new("Visitor Code is longer than 255 chars.")
|
44
60
|
end
|
45
|
-
default_visitor_code
|
61
|
+
default_visitor_code
|
46
62
|
end
|
47
63
|
|
48
64
|
def read(cookies)
|
@@ -54,10 +70,10 @@ module Kameleoon
|
|
54
70
|
start_index = KAMELEOON_KEY_JS_COOKIE.length
|
55
71
|
value = value[start_index..-1]
|
56
72
|
end
|
57
|
-
if value.length
|
58
|
-
|
73
|
+
if value.length == 0
|
74
|
+
nil
|
59
75
|
end
|
60
|
-
value[0..(
|
76
|
+
value[0..(KAMELEOON_VISITOR_CODE_LENGTH - 1)]
|
61
77
|
end
|
62
78
|
end
|
63
79
|
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
|
@@ -45,5 +45,10 @@ module Kameleoon
|
|
45
45
|
super("Visitor " + visitor_code + " is not activated.")
|
46
46
|
end
|
47
47
|
end
|
48
|
+
class VisitorCodeNotValid < KameleoonError
|
49
|
+
def initialize(message = "")
|
50
|
+
super("Visitor code not valid: " + message)
|
51
|
+
end
|
52
|
+
end
|
48
53
|
end
|
49
54
|
end
|
@@ -0,0 +1,66 @@
|
|
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 } 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
|
+
"condition": {
|
18
|
+
"field": "type",
|
19
|
+
"operator": "IN",
|
20
|
+
"parameters": ["SERVER_SIDE", "HYBRID"]
|
21
|
+
}
|
22
|
+
}, {
|
23
|
+
"condition": {
|
24
|
+
"field": "siteCode",
|
25
|
+
"operator": "IN",
|
26
|
+
"parameters": ["' + site_code + '"]
|
27
|
+
}
|
28
|
+
}]
|
29
|
+
},
|
30
|
+
"sort": [{
|
31
|
+
"field": "id",
|
32
|
+
"direction": "ASC"
|
33
|
+
}]
|
34
|
+
}
|
35
|
+
}'
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.query_feature_flags(site_code)
|
39
|
+
'{
|
40
|
+
"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 } }",
|
42
|
+
"variables": {
|
43
|
+
"filter": {
|
44
|
+
"and": [{
|
45
|
+
"condition": {
|
46
|
+
"field": "featureStatus",
|
47
|
+
"operator": "IN",
|
48
|
+
"parameters": ["ACTIVATED", "SCHEDULED", "DEACTIVATED"]
|
49
|
+
}
|
50
|
+
}, {
|
51
|
+
"condition": {
|
52
|
+
"field": "siteCode",
|
53
|
+
"operator": "IN",
|
54
|
+
"parameters": ["' + site_code + '"]
|
55
|
+
}
|
56
|
+
}]
|
57
|
+
},
|
58
|
+
"sort": [{
|
59
|
+
"field": "id",
|
60
|
+
"direction": "ASC"
|
61
|
+
}]
|
62
|
+
}
|
63
|
+
}'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
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,17 +19,17 @@ 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)
|
@@ -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.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Kameleoon
|
7
|
+
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-25 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
|