kameleoon-client-ruby 1.0.9 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 887e6709dff33fa410a8e34a32577d8ad1ffe56fa52cadd24bd1734c68d2e46b
4
- data.tar.gz: e42d4d944e8c1549eae488f74e7b5c8891bf41f35dcce02133bfc68cee969463
3
+ metadata.gz: 3e62c8e4dfa35f91706257ff5367a266736af7c03786803ad8f9fff06aa8c194
4
+ data.tar.gz: 7773e082be938108b42c65cd0109392413bf938619cf04cc994617111c746f15
5
5
  SHA512:
6
- metadata.gz: 3cf8ee8561091145b530082e2ce86614b41f9c63c70b644518c5a9d3738e659084526003ad298cdf320da0570cc7d55b551b3b80e680877a1c4c0bd984bc1b9f
7
- data.tar.gz: 4c9e4873467d1f2578be20f8246a47ad3810401251fb9e3dd34d3baad073184c6083fa0a35893c95e308a3482ebb3b42d58e1fd8e82c9400fa554ebbd1e16d63
6
+ metadata.gz: d74b03762402a1f4c6e7349bb085e3b185ee564f0a7f645a16f7bb8da0321b73689adad785d52bdce48e9a0646f6b10de6c3f2fc8016b9459b713eafbda77e6f
7
+ data.tar.gz: a835f8b23265a2ae8241114fb2c5fe1f8965544fb348c2f6c16d714fe543fa6396b5bc6cbf7f68d8ac5e5391b8cbf594f55086e8c24290fda57a7b18ac5f57c4
@@ -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
- @interval = config['actions_configuration_refresh_interval'].to_s + 'm' || interval
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 <= feature_flag['expositionRate']
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 ] variable_key
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
- API_SSX_URL = "https://api-ssx.kameleoon.com"
350
+
351
+ API_SSX_URL = 'https://api-ssx.kameleoon.com'
318
352
  REFERENCE = 0
319
- STATUS_ACTIVE = "ACTIVE"
320
- FEATURE_STATUS_DEACTIVATED = "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("Scheduled job to fetch configuration is starting.")
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("Start-up, fetching is starting")
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}}
@@ -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
@@ -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",
@@ -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).aget request_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 }.first
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
@@ -1,3 +1,3 @@
1
1
  module Kameleoon
2
- VERSION = '1.0.9'
2
+ VERSION = '1.1.1'
3
3
  end
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.9
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: 2021-11-25 00:00:00.000000000 Z
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