prefab-cloud-ruby 0.24.5 → 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 +4 -4
 - data/CHANGELOG.md +15 -0
 - data/VERSION +1 -1
 - data/compile_protos.sh +7 -0
 - data/lib/prefab/client.rb +20 -46
 - data/lib/prefab/config_client.rb +9 -12
 - data/lib/prefab/config_resolver.rb +2 -1
 - data/lib/prefab/config_value_unwrapper.rb +20 -9
 - data/lib/prefab/context.rb +43 -7
 - data/lib/prefab/context_shape_aggregator.rb +1 -1
 - data/lib/prefab/criteria_evaluator.rb +24 -16
 - data/lib/prefab/evaluation.rb +48 -0
 - data/lib/prefab/evaluation_summary_aggregator.rb +85 -0
 - data/lib/prefab/example_contexts_aggregator.rb +76 -0
 - data/lib/prefab/exponential_backoff.rb +5 -0
 - data/lib/prefab/feature_flag_client.rb +0 -2
 - data/lib/prefab/log_path_aggregator.rb +1 -1
 - data/lib/prefab/logger_client.rb +12 -13
 - data/lib/prefab/options.rb +52 -43
 - data/lib/prefab/periodic_sync.rb +30 -13
 - data/lib/prefab/rate_limit_cache.rb +41 -0
 - data/lib/prefab/resolved_config_presenter.rb +2 -4
 - data/lib/prefab/weighted_value_resolver.rb +1 -1
 - data/lib/prefab-cloud-ruby.rb +5 -5
 - data/lib/prefab_pb.rb +11 -1
 - data/prefab-cloud-ruby.gemspec +14 -9
 - data/test/integration_test.rb +1 -3
 - data/test/integration_test_helpers.rb +0 -1
 - data/test/support/common_helpers.rb +174 -0
 - data/test/support/mock_base_client.rb +44 -0
 - data/test/support/mock_config_client.rb +19 -0
 - data/test/support/mock_config_loader.rb +1 -0
 - data/test/test_client.rb +354 -40
 - data/test/test_config_client.rb +1 -0
 - data/test/test_config_loader.rb +1 -0
 - data/test/test_config_resolver.rb +25 -24
 - data/test/test_config_value_unwrapper.rb +22 -32
 - data/test/test_context.rb +1 -0
 - data/test/test_context_shape_aggregator.rb +11 -1
 - data/test/test_criteria_evaluator.rb +180 -133
 - data/test/test_evaluation_summary_aggregator.rb +162 -0
 - data/test/test_example_contexts_aggregator.rb +238 -0
 - data/test/test_helper.rb +5 -131
 - data/test/test_integration.rb +6 -4
 - data/test/test_local_config_parser.rb +2 -2
 - data/test/test_log_path_aggregator.rb +9 -1
 - data/test/test_logger.rb +6 -5
 - data/test/test_options.rb +33 -2
 - data/test/test_rate_limit_cache.rb +44 -0
 - data/test/test_weighted_value_resolver.rb +13 -7
 - metadata +13 -8
 - data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
 - data/lib/prefab/evaluated_keys_aggregator.rb +0 -41
 - data/lib/prefab/noop_cache.rb +0 -15
 - data/lib/prefab/noop_stats.rb +0 -8
 - data/test/test_evaluated_configs_aggregator.rb +0 -254
 - data/test/test_evaluated_keys_aggregator.rb +0 -54
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 318e4b03e0089cfe68fa20b00e3c400c1005b739f63ed4183b071418342e4bbe
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 46e467acdeb1dea78cc2e5c97031c952fa6c2caf9be235a217d9341f54505389
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: fac2499f7fc5f4ba46eb6bb0f8a8671ac6fa8412600e8f3fbdeb61db68b60f491def31bd7faf1ee78711644ff4e1c253386c54e08d40f075404ab0b22d5977db
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 9b112dcdbe22b2cadc6c20e5d10df94b1f1e269f49e3a12ada73550294a55154db098cc3662c5e25f2d569111b870f09df6abf2fc7af1cbc447983b3b3378bce
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,20 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Changelog
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ## 1.0.0 - 2023-08-10
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            - Removed EvaluatedKeysAggregator (#137)
         
     | 
| 
      
 6 
     | 
    
         
            +
            - Change `collect_evaluation_summaries` default to true (#136)
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Removed some backwards compatibility shims (#133)
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Standardizing options (#132)
         
     | 
| 
      
 9 
     | 
    
         
            +
              - Note that the default value for `context_upload_mode` is `:periodic_example` which means example contexts will be collected.
         
     | 
| 
      
 10 
     | 
    
         
            +
                This enables easy variant override assignment in our UI. More at https://prefab.cloud/blog/feature-flag-variant-assignment/
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ## 0.24.6 - 2023-07-31
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            - Logger Client compatibility (#129)
         
     | 
| 
      
 15 
     | 
    
         
            +
            - Replace EvaluatedConfigs with ExampleContexts (#128)
         
     | 
| 
      
 16 
     | 
    
         
            +
            - Add ConfigEvaluationSummaries (opt-in for now) (#123)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       3 
18 
     | 
    
         
             
            ## 0.24.5 - 2023-07-10
         
     | 
| 
       4 
19 
     | 
    
         | 
| 
       5 
20 
     | 
    
         
             
            - Report Client Version (#121)
         
     | 
    
        data/VERSION
    CHANGED
    
    | 
         @@ -1 +1 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            0. 
     | 
| 
      
 1 
     | 
    
         
            +
            1.0.0
         
     | 
    
        data/compile_protos.sh
    CHANGED
    
    | 
         @@ -1,7 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            #!/usr/bin/env bash
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            set -e
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            gem install grpc-tools
         
     | 
| 
       4 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
            (
         
     | 
| 
      
 8 
     | 
    
         
            +
              cd ../prefab-cloud
         
     | 
| 
      
 9 
     | 
    
         
            +
              git pull --rebase
         
     | 
| 
      
 10 
     | 
    
         
            +
            )
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
       5 
12 
     | 
    
         
             
            grpc_tools_ruby_protoc -I ../prefab-cloud/ --ruby_out=lib --grpc_out=lib prefab.proto
         
     | 
| 
       6 
13 
     | 
    
         | 
| 
       7 
14 
     | 
    
         
             
            gsed -i 's/^module Prefab$/module PrefabProto/g' lib/prefab_pb.rb
         
     | 
    
        data/lib/prefab/client.rb
    CHANGED
    
    | 
         @@ -7,12 +7,10 @@ module Prefab 
     | 
|
| 
       7 
7 
     | 
    
         
             
                MAX_SLEEP_SEC = 10
         
     | 
| 
       8 
8 
     | 
    
         
             
                BASE_SLEEP_SEC = 0.5
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                attr_reader : 
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :namespace, :interceptor, :api_key, :prefab_api_url, :options, :instance_hash
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                def initialize(options = Prefab::Options.new)
         
     | 
| 
       13 
13 
     | 
    
         
             
                  @options = options.is_a?(Prefab::Options) ? options : Prefab::Options.new(options)
         
     | 
| 
       14 
     | 
    
         
            -
                  @shared_cache = @options.shared_cache
         
     | 
| 
       15 
     | 
    
         
            -
                  @stats = @options.stats
         
     | 
| 
       16 
14 
     | 
    
         
             
                  @namespace = @options.namespace
         
     | 
| 
       17 
15 
     | 
    
         
             
                  @stubs = {}
         
     | 
| 
       18 
16 
     | 
    
         
             
                  @instance_hash = UUID.new.generate
         
     | 
| 
         @@ -32,11 +30,6 @@ module Prefab 
     | 
|
| 
       32 
30 
     | 
    
         
             
                  config_client
         
     | 
| 
       33 
31 
     | 
    
         
             
                end
         
     | 
| 
       34 
32 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                def with_log_context(_lookup_key, properties, &block)
         
     | 
| 
       36 
     | 
    
         
            -
                  warn '[DEPRECATION] `$prefab.with_log_context` is deprecated.  Please use `with_context` instead.'
         
     | 
| 
       37 
     | 
    
         
            -
                  with_context(properties, &block)
         
     | 
| 
       38 
     | 
    
         
            -
                end
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
33 
     | 
    
         
             
                def with_context(properties, &block)
         
     | 
| 
       41 
34 
     | 
    
         
             
                  Prefab::Context.with_context(properties, &block)
         
     | 
| 
       42 
35 
     | 
    
         
             
                end
         
     | 
| 
         @@ -73,18 +66,24 @@ module Prefab 
     | 
|
| 
       73 
66 
     | 
    
         
             
                                                                           sync_interval: @options.collect_sync_interval)
         
     | 
| 
       74 
67 
     | 
    
         
             
                end
         
     | 
| 
       75 
68 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
                def  
     | 
| 
       77 
     | 
    
         
            -
                  return nil if @options. 
     | 
| 
      
 69 
     | 
    
         
            +
                def example_contexts_aggregator
         
     | 
| 
      
 70 
     | 
    
         
            +
                  return nil if @options.collect_max_example_contexts <= 0
         
     | 
| 
       78 
71 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
                  @ 
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
      
 72 
     | 
    
         
            +
                  @example_contexts_aggregator ||= ExampleContextsAggregator.new(
         
     | 
| 
      
 73 
     | 
    
         
            +
                    client: self,
         
     | 
| 
      
 74 
     | 
    
         
            +
                    max_contexts: @options.collect_max_example_contexts,
         
     | 
| 
      
 75 
     | 
    
         
            +
                    sync_interval: @options.collect_sync_interval
         
     | 
| 
      
 76 
     | 
    
         
            +
                  )
         
     | 
| 
       81 
77 
     | 
    
         
             
                end
         
     | 
| 
       82 
78 
     | 
    
         | 
| 
       83 
     | 
    
         
            -
                def  
     | 
| 
       84 
     | 
    
         
            -
                  return nil if @options. 
     | 
| 
      
 79 
     | 
    
         
            +
                def evaluation_summary_aggregator
         
     | 
| 
      
 80 
     | 
    
         
            +
                  return nil if @options.collect_max_evaluation_summaries <= 0
         
     | 
| 
       85 
81 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
                  @ 
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
      
 82 
     | 
    
         
            +
                  @evaluation_summary_aggregator ||= EvaluationSummaryAggregator.new(
         
     | 
| 
      
 83 
     | 
    
         
            +
                    client: self,
         
     | 
| 
      
 84 
     | 
    
         
            +
                    max_keys: @options.collect_max_evaluation_summaries,
         
     | 
| 
      
 85 
     | 
    
         
            +
                    sync_interval: @options.collect_sync_interval
         
     | 
| 
      
 86 
     | 
    
         
            +
                  )
         
     | 
| 
       88 
87 
     | 
    
         
             
                end
         
     | 
| 
       89 
88 
     | 
    
         | 
| 
       90 
89 
     | 
    
         
             
                def set_rails_loggers
         
     | 
| 
         @@ -104,19 +103,15 @@ module Prefab 
     | 
|
| 
       104 
103 
     | 
    
         
             
                  log.log_internal msg, path, nil, level
         
     | 
| 
       105 
104 
     | 
    
         
             
                end
         
     | 
| 
       106 
105 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
                def enabled?(feature_name,  
     | 
| 
       108 
     | 
    
         
            -
                   
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
                  feature_flag_client.feature_is_on_for?(feature_name, properties)
         
     | 
| 
      
 106 
     | 
    
         
            +
                def enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  feature_flag_client.feature_is_on_for?(feature_name, jit_context)
         
     | 
| 
       111 
108 
     | 
    
         
             
                end
         
     | 
| 
       112 
109 
     | 
    
         | 
| 
       113 
     | 
    
         
            -
                def get(key,  
     | 
| 
      
 110 
     | 
    
         
            +
                def get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED)
         
     | 
| 
       114 
111 
     | 
    
         
             
                  if is_ff?(key)
         
     | 
| 
       115 
     | 
    
         
            -
                     
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
                    feature_flag_client.get(key, properties, default: ff_default)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    feature_flag_client.get(key, jit_context, default: default)
         
     | 
| 
       118 
113 
     | 
    
         
             
                  else
         
     | 
| 
       119 
     | 
    
         
            -
                    config_client.get(key,  
     | 
| 
      
 114 
     | 
    
         
            +
                    config_client.get(key, default, jit_context)
         
     | 
| 
       120 
115 
     | 
    
         
             
                  end
         
     | 
| 
       121 
116 
     | 
    
         
             
                end
         
     | 
| 
       122 
117 
     | 
    
         | 
| 
         @@ -148,26 +143,5 @@ module Prefab 
     | 
|
| 
       148 
143 
     | 
    
         | 
| 
       149 
144 
     | 
    
         
             
                  raw && raw.allowable_values.any?
         
     | 
| 
       150 
145 
     | 
    
         
             
                end
         
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
                # The goal here is to ease transition from the old API to the new one. The
         
     | 
| 
       153 
     | 
    
         
            -
                # old API had a lookup_key parameter that is deprecated. This method
         
     | 
| 
       154 
     | 
    
         
            -
                # handles the transition by checking if the first parameter is a string and
         
     | 
| 
       155 
     | 
    
         
            -
                # if so, it is assumed to be the lookup_key and a deprecation warning is
         
     | 
| 
       156 
     | 
    
         
            -
                # issued and we know the second argument is the properties. If the first
         
     | 
| 
       157 
     | 
    
         
            -
                # parameter is a hash, you're on the new API and no further action is
         
     | 
| 
       158 
     | 
    
         
            -
                # required.
         
     | 
| 
       159 
     | 
    
         
            -
                def handle_positional_arguments(lookup_key, properties, method)
         
     | 
| 
       160 
     | 
    
         
            -
                  # handle JIT context
         
     | 
| 
       161 
     | 
    
         
            -
                  if lookup_key.is_a?(Hash) && properties == NO_DEFAULT_PROVIDED
         
     | 
| 
       162 
     | 
    
         
            -
                    properties = lookup_key
         
     | 
| 
       163 
     | 
    
         
            -
                    lookup_key = nil
         
     | 
| 
       164 
     | 
    
         
            -
                  end
         
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
                  if lookup_key.is_a?(String)
         
     | 
| 
       167 
     | 
    
         
            -
                    warn "[DEPRECATION] `$prefab.#{method}`'s lookup_key argument is deprecated. Please remove it or use context instead."
         
     | 
| 
       168 
     | 
    
         
            -
                  end
         
     | 
| 
       169 
     | 
    
         
            -
             
     | 
| 
       170 
     | 
    
         
            -
                  [lookup_key, properties]
         
     | 
| 
       171 
     | 
    
         
            -
                end
         
     | 
| 
       172 
146 
     | 
    
         
             
              end
         
     | 
| 
       173 
147 
     | 
    
         
             
            end
         
     | 
    
        data/lib/prefab/config_client.rb
    CHANGED
    
    | 
         @@ -58,18 +58,16 @@ module Prefab 
     | 
|
| 
       58 
58 
     | 
    
         
             
                def get(key, default = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED)
         
     | 
| 
       59 
59 
     | 
    
         
             
                  context = @config_resolver.make_context(properties)
         
     | 
| 
       60 
60 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                   
     | 
| 
      
 61 
     | 
    
         
            +
                  if !context.blank? && @base_client.example_contexts_aggregator
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @base_client.example_contexts_aggregator.record(context)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
       62 
64 
     | 
    
         | 
| 
       63 
     | 
    
         
            -
                   
     | 
| 
       64 
     | 
    
         
            -
                  @base_client.evaluated_keys_aggregator&.push(key)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  evaluation = _get(key, context)
         
     | 
| 
       65 
66 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
                   
     | 
| 
       67 
     | 
    
         
            -
                    # NOTE: we don't &.push here because some of the args aren't already available
         
     | 
| 
       68 
     | 
    
         
            -
                    if @base_client.evaluated_configs_aggregator && key != Prefab::LoggerClient::BASE_KEY && !key.start_with?(LOGGING_KEY_PREFIX)
         
     | 
| 
       69 
     | 
    
         
            -
                      @base_client.evaluated_configs_aggregator.push([raw(key), value, context])
         
     | 
| 
       70 
     | 
    
         
            -
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @base_client.context_shape_aggregator&.push(context)
         
     | 
| 
       71 
68 
     | 
    
         | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
                  if evaluation
         
     | 
| 
      
 70 
     | 
    
         
            +
                    evaluation.report_and_return(@base_client.evaluation_summary_aggregator)
         
     | 
| 
       73 
71 
     | 
    
         
             
                  else
         
     | 
| 
       74 
72 
     | 
    
         
             
                    handle_default(key, default)
         
     | 
| 
       75 
73 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -90,7 +88,7 @@ module Prefab 
     | 
|
| 
       90 
88 
     | 
    
         
             
                end
         
     | 
| 
       91 
89 
     | 
    
         | 
| 
       92 
90 
     | 
    
         
             
                def _get(key, properties)
         
     | 
| 
       93 
     | 
    
         
            -
                  # wait timeout sec for the  
     | 
| 
      
 91 
     | 
    
         
            +
                  # wait timeout sec for the initialization to be complete
         
     | 
| 
       94 
92 
     | 
    
         
             
                  @initialized_future.value(@options.initialization_timeout_sec)
         
     | 
| 
       95 
93 
     | 
    
         
             
                  if @initialized_future.incomplete?
         
     | 
| 
       96 
94 
     | 
    
         
             
                    unless @options.on_init_failure == Prefab::Options::ON_INITIALIZATION_FAILURE::RETURN
         
     | 
| 
         @@ -158,7 +156,6 @@ module Prefab 
     | 
|
| 
       158 
156 
     | 
    
         
             
                    @base_client.log_internal ::Logger::DEBUG,
         
     | 
| 
       159 
157 
     | 
    
         
             
                                              "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", 'load_configs'
         
     | 
| 
       160 
158 
     | 
    
         
             
                  end
         
     | 
| 
       161 
     | 
    
         
            -
                  @base_client.stats.increment('prefab.config.checkpoint.load')
         
     | 
| 
       162 
159 
     | 
    
         
             
                  @config_resolver.update
         
     | 
| 
       163 
160 
     | 
    
         
             
                  finish_init!(source)
         
     | 
| 
       164 
161 
     | 
    
         
             
                end
         
     | 
| 
         @@ -183,7 +180,7 @@ module Prefab 
     | 
|
| 
       183 
180 
     | 
    
         | 
| 
       184 
181 
     | 
    
         
             
                  @base_client.log_internal ::Logger::INFO, "Unlocked Config via #{source}"
         
     | 
| 
       185 
182 
     | 
    
         
             
                  @initialization_lock.release_write_lock
         
     | 
| 
       186 
     | 
    
         
            -
                  @base_client.log. 
     | 
| 
      
 183 
     | 
    
         
            +
                  @base_client.log.config_client = self
         
     | 
| 
       187 
184 
     | 
    
         
             
                  @base_client.log_internal ::Logger::INFO, to_s
         
     | 
| 
       188 
185 
     | 
    
         
             
                end
         
     | 
| 
       189 
186 
     | 
    
         | 
| 
         @@ -10,6 +10,7 @@ module Prefab 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  @config_loader = config_loader
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @project_env_id = 0 # we don't know this yet, it is set from the API results
         
     | 
| 
       12 
12 
     | 
    
         
             
                  @base_client = base_client
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @on_update = nil
         
     | 
| 
       13 
14 
     | 
    
         
             
                  make_local
         
     | 
| 
       14 
15 
     | 
    
         
             
                end
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
         @@ -54,7 +55,7 @@ module Prefab 
     | 
|
| 
       54 
55 
     | 
    
         
             
                end
         
     | 
| 
       55 
56 
     | 
    
         | 
| 
       56 
57 
     | 
    
         
             
                def make_context(properties)
         
     | 
| 
       57 
     | 
    
         
            -
                  if properties == NO_DEFAULT_PROVIDED
         
     | 
| 
      
 58 
     | 
    
         
            +
                  if properties == NO_DEFAULT_PROVIDED || properties.nil?
         
     | 
| 
       58 
59 
     | 
    
         
             
                    Context.current
         
     | 
| 
       59 
60 
     | 
    
         
             
                  elsif properties.is_a?(Context)
         
     | 
| 
       60 
61 
     | 
    
         
             
                    properties
         
     | 
| 
         @@ -2,24 +2,35 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module Prefab
         
     | 
| 
       4 
4 
     | 
    
         
             
              class ConfigValueUnwrapper
         
     | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
       6 
     | 
    
         
            -
                  return nil unless config_value
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :value, :weighted_value_index
         
     | 
| 
       7 
6 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(value, weighted_value_index = nil)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @value = value
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @weighted_value_index = weighted_value_index
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def unwrap
         
     | 
| 
      
 13 
     | 
    
         
            +
                  case value.type
         
     | 
| 
       9 
14 
     | 
    
         
             
                  when :int, :string, :double, :bool, :log_level
         
     | 
| 
       10 
     | 
    
         
            -
                     
     | 
| 
      
 15 
     | 
    
         
            +
                    value.public_send(value.type)
         
     | 
| 
       11 
16 
     | 
    
         
             
                  when :string_list
         
     | 
| 
       12 
     | 
    
         
            -
                     
     | 
| 
       13 
     | 
    
         
            -
                   
     | 
| 
       14 
     | 
    
         
            -
                     
     | 
| 
      
 17 
     | 
    
         
            +
                    value.string_list.values
         
     | 
| 
      
 18 
     | 
    
         
            +
                  else
         
     | 
| 
      
 19 
     | 
    
         
            +
                    raise "Unknown type: #{config_value.type}"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def self.deepest_value(config_value, config_key, context)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  if config_value&.type == :weighted_values
         
     | 
| 
      
 25 
     | 
    
         
            +
                    value, index = Prefab::WeightedValueResolver.new(
         
     | 
| 
       15 
26 
     | 
    
         
             
                      config_value.weighted_values.weighted_values,
         
     | 
| 
       16 
27 
     | 
    
         
             
                      config_key,
         
     | 
| 
       17 
28 
     | 
    
         
             
                      context.get(config_value.weighted_values.hash_by_property_name)
         
     | 
| 
       18 
29 
     | 
    
         
             
                    ).resolve
         
     | 
| 
       19 
30 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
      
 31 
     | 
    
         
            +
                    new(deepest_value(value.value, config_key, context).value, index)
         
     | 
| 
       21 
32 
     | 
    
         
             
                  else
         
     | 
| 
       22 
     | 
    
         
            -
                     
     | 
| 
      
 33 
     | 
    
         
            +
                    new(config_value)
         
     | 
| 
       23 
34 
     | 
    
         
             
                  end
         
     | 
| 
       24 
35 
     | 
    
         
             
                end
         
     | 
| 
       25 
36 
     | 
    
         
             
              end
         
     | 
    
        data/lib/prefab/context.rb
    CHANGED
    
    | 
         @@ -25,10 +25,23 @@ module Prefab 
     | 
|
| 
       25 
25 
     | 
    
         
             
                  def to_h
         
     | 
| 
       26 
26 
     | 
    
         
             
                    @hash
         
     | 
| 
       27 
27 
     | 
    
         
             
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def key
         
     | 
| 
      
 30 
     | 
    
         
            +
                    "#{@name}:#{get('key')}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def to_proto
         
     | 
| 
      
 34 
     | 
    
         
            +
                    PrefabProto::Context.new(
         
     | 
| 
      
 35 
     | 
    
         
            +
                      type: name,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      values: to_h.transform_values do |value|
         
     | 
| 
      
 37 
     | 
    
         
            +
                        ConfigValueWrapper.wrap(value)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    )
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
       28 
41 
     | 
    
         
             
                end
         
     | 
| 
       29 
42 
     | 
    
         | 
| 
       30 
43 
     | 
    
         
             
                THREAD_KEY = :prefab_context
         
     | 
| 
       31 
     | 
    
         
            -
                attr_reader :contexts
         
     | 
| 
      
 44 
     | 
    
         
            +
                attr_reader :contexts, :seen_at
         
     | 
| 
       32 
45 
     | 
    
         | 
| 
       33 
46 
     | 
    
         
             
                class << self
         
     | 
| 
       34 
47 
     | 
    
         
             
                  def current=(context)
         
     | 
| 
         @@ -58,6 +71,7 @@ module Prefab 
     | 
|
| 
       58 
71 
     | 
    
         | 
| 
       59 
72 
     | 
    
         
             
                def initialize(context = {})
         
     | 
| 
       60 
73 
     | 
    
         
             
                  @contexts = {}
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @seen_at = Time.now.utc.to_i
         
     | 
| 
       61 
75 
     | 
    
         | 
| 
       62 
76 
     | 
    
         
             
                  if context.is_a?(NamedContext)
         
     | 
| 
       63 
77 
     | 
    
         
             
                    @contexts[context.name] = context
         
     | 
| 
         @@ -77,6 +91,10 @@ module Prefab 
     | 
|
| 
       77 
91 
     | 
    
         
             
                  end
         
     | 
| 
       78 
92 
     | 
    
         
             
                end
         
     | 
| 
       79 
93 
     | 
    
         | 
| 
      
 94 
     | 
    
         
            +
                def blank?
         
     | 
| 
      
 95 
     | 
    
         
            +
                  contexts.empty?
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
       80 
98 
     | 
    
         
             
                def set(name, hash)
         
     | 
| 
       81 
99 
     | 
    
         
             
                  @contexts[name.to_s] = NamedContext.new(name, hash)
         
     | 
| 
       82 
100 
     | 
    
         
             
                end
         
     | 
| 
         @@ -113,15 +131,33 @@ module Prefab 
     | 
|
| 
       113 
131 
     | 
    
         | 
| 
       114 
132 
     | 
    
         
             
                  PrefabProto::ContextSet.new(
         
     | 
| 
       115 
133 
     | 
    
         
             
                    contexts: contexts.map do |name, context|
         
     | 
| 
       116 
     | 
    
         
            -
                       
     | 
| 
       117 
     | 
    
         
            -
                        type: name,
         
     | 
| 
       118 
     | 
    
         
            -
                        values: context.to_h.transform_values do |value|
         
     | 
| 
       119 
     | 
    
         
            -
                          ConfigValueWrapper.wrap(value)
         
     | 
| 
       120 
     | 
    
         
            -
                        end
         
     | 
| 
       121 
     | 
    
         
            -
                      )
         
     | 
| 
      
 134 
     | 
    
         
            +
                      context.to_proto
         
     | 
| 
       122 
135 
     | 
    
         
             
                    end.concat([PrefabProto::Context.new(type: 'prefab',
         
     | 
| 
       123 
136 
     | 
    
         
             
                                                         values: prefab_context)])
         
     | 
| 
       124 
137 
     | 
    
         
             
                  )
         
     | 
| 
       125 
138 
     | 
    
         
             
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                def slim_proto
         
     | 
| 
      
 141 
     | 
    
         
            +
                  PrefabProto::ContextSet.new(
         
     | 
| 
      
 142 
     | 
    
         
            +
                    contexts: contexts.map do |_, context|
         
     | 
| 
      
 143 
     | 
    
         
            +
                      context.to_proto
         
     | 
| 
      
 144 
     | 
    
         
            +
                    end
         
     | 
| 
      
 145 
     | 
    
         
            +
                  )
         
     | 
| 
      
 146 
     | 
    
         
            +
                end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                def grouped_key
         
     | 
| 
      
 149 
     | 
    
         
            +
                  contexts.map do |_, context|
         
     | 
| 
      
 150 
     | 
    
         
            +
                    context.key
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end.sort.join('|')
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                include Comparable
         
     | 
| 
      
 155 
     | 
    
         
            +
                def <=>(other)
         
     | 
| 
      
 156 
     | 
    
         
            +
                  if other.is_a?(Prefab::Context)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    to_h <=> other.to_h
         
     | 
| 
      
 158 
     | 
    
         
            +
                  else
         
     | 
| 
      
 159 
     | 
    
         
            +
                    super
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
       126 
162 
     | 
    
         
             
              end
         
     | 
| 
       127 
163 
     | 
    
         
             
            end
         
     | 
| 
         @@ -4,6 +4,8 @@ 
     | 
|
| 
       4 
4 
     | 
    
         
             
            # We're intentionally keeping the UPCASED method names to match the protobuf
         
     | 
| 
       5 
5 
     | 
    
         
             
            # and avoid wasting CPU cycles lowercasing things
         
     | 
| 
       6 
6 
     | 
    
         
             
            module Prefab
         
     | 
| 
      
 7 
     | 
    
         
            +
              # This class evaluates a config's criteria. `evaluate` returns the value of
         
     | 
| 
      
 8 
     | 
    
         
            +
              # the first match based on the provided properties.
         
     | 
| 
       7 
9 
     | 
    
         
             
              class CriteriaEvaluator
         
     | 
| 
       8 
10 
     | 
    
         
             
                NAMESPACE_KEY = 'NAMESPACE'
         
     | 
| 
       9 
11 
     | 
    
         
             
                NO_MATCHING_ROWS = [].freeze
         
     | 
| 
         @@ -17,15 +19,8 @@ module Prefab 
     | 
|
| 
       17 
19 
     | 
    
         
             
                end
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
21 
     | 
    
         
             
                def evaluate(properties)
         
     | 
| 
       20 
     | 
    
         
            -
                   
     | 
| 
       21 
     | 
    
         
            -
                     
     | 
| 
       22 
     | 
    
         
            -
                  end
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                  default_row_values.each do |conditional_value|
         
     | 
| 
       25 
     | 
    
         
            -
                    return conditional_value.value if all_criteria_match?(conditional_value, properties)
         
     | 
| 
       26 
     | 
    
         
            -
                  end
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                  nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                  evaluate_for_env(@project_env_id, properties) ||
         
     | 
| 
      
 23 
     | 
    
         
            +
                    evaluate_for_env(0, properties)
         
     | 
| 
       29 
24 
     | 
    
         
             
                end
         
     | 
| 
       30 
25 
     | 
    
         | 
| 
       31 
26 
     | 
    
         
             
                def all_criteria_match?(conditional_value, props)
         
     | 
| 
         @@ -83,12 +78,24 @@ module Prefab 
     | 
|
| 
       83 
78 
     | 
    
         | 
| 
       84 
79 
     | 
    
         
             
                private
         
     | 
| 
       85 
80 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
                def  
     | 
| 
       87 
     | 
    
         
            -
                  @config.rows. 
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
      
 81 
     | 
    
         
            +
                def evaluate_for_env(env_id, properties)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  @config.rows.each_with_index do |row, index|
         
     | 
| 
      
 83 
     | 
    
         
            +
                    next unless row.project_env_id == env_id
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    row.values.each_with_index do |conditional_value, value_index|
         
     | 
| 
      
 86 
     | 
    
         
            +
                      next unless all_criteria_match?(conditional_value, properties)
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                      return Prefab::Evaluation.new(
         
     | 
| 
      
 89 
     | 
    
         
            +
                        config: @config,
         
     | 
| 
      
 90 
     | 
    
         
            +
                        value: conditional_value.value,
         
     | 
| 
      
 91 
     | 
    
         
            +
                        value_index: value_index,
         
     | 
| 
      
 92 
     | 
    
         
            +
                        config_row_index: index,
         
     | 
| 
      
 93 
     | 
    
         
            +
                        context: properties
         
     | 
| 
      
 94 
     | 
    
         
            +
                      )
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                  end
         
     | 
| 
       89 
97 
     | 
    
         | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                  @config.rows.find { |row| row.project_env_id != @project_env_id }&.values || NO_MATCHING_ROWS
         
     | 
| 
      
 98 
     | 
    
         
            +
                  nil
         
     | 
| 
       92 
99 
     | 
    
         
             
                end
         
     | 
| 
       93 
100 
     | 
    
         | 
| 
       94 
101 
     | 
    
         
             
                def in_segment?(criterion, properties)
         
     | 
| 
         @@ -96,11 +103,12 @@ module Prefab 
     | 
|
| 
       96 
103 
     | 
    
         | 
| 
       97 
104 
     | 
    
         
             
                  @base_client.log.info("Segment #{criterion.value_to_match.string} not found") unless segment
         
     | 
| 
       98 
105 
     | 
    
         | 
| 
       99 
     | 
    
         
            -
                  segment&. 
     | 
| 
      
 106 
     | 
    
         
            +
                  segment&.report_and_return(@base_client.evaluation_summary_aggregator)
         
     | 
| 
       100 
107 
     | 
    
         
             
                end
         
     | 
| 
       101 
108 
     | 
    
         | 
| 
       102 
109 
     | 
    
         
             
                def matches?(criterion, value, properties)
         
     | 
| 
       103 
     | 
    
         
            -
                  criterion_value_or_values = Prefab::ConfigValueUnwrapper. 
     | 
| 
      
 110 
     | 
    
         
            +
                  criterion_value_or_values = Prefab::ConfigValueUnwrapper.deepest_value(criterion.value_to_match, @config.key,
         
     | 
| 
      
 111 
     | 
    
         
            +
                                                                                         properties).unwrap
         
     | 
| 
       104 
112 
     | 
    
         | 
| 
       105 
113 
     | 
    
         
             
                  case criterion_value_or_values
         
     | 
| 
       106 
114 
     | 
    
         
             
                  when Google::Protobuf::RepeatedField
         
     | 
| 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Prefab
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Records the result of evaluating a config's criteria and forensics for reporting
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Evaluation
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_reader :value
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(config:, value:, value_index:, config_row_index:, context:)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @config = config
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @value = value
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @value_index = value_index
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @config_row_index = config_row_index
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @context = context
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def unwrapped_value
         
     | 
| 
      
 17 
     | 
    
         
            +
                  deepest_value.unwrap
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def report_and_return(evaluation_summary_aggregator)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  report(evaluation_summary_aggregator)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  unwrapped_value
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def report(evaluation_summary_aggregator)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  return if @config.config_type == :LOG_LEVEL
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  evaluation_summary_aggregator&.record(
         
     | 
| 
      
 32 
     | 
    
         
            +
                    config_key: @config.key,
         
     | 
| 
      
 33 
     | 
    
         
            +
                    config_type: @config.config_type,
         
     | 
| 
      
 34 
     | 
    
         
            +
                    counter: {
         
     | 
| 
      
 35 
     | 
    
         
            +
                      config_id: @config.id,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      config_row_index: @config_row_index,
         
     | 
| 
      
 37 
     | 
    
         
            +
                      conditional_value_index: @value_index,
         
     | 
| 
      
 38 
     | 
    
         
            +
                      selected_value: deepest_value.value,
         
     | 
| 
      
 39 
     | 
    
         
            +
                      weighted_value_index: deepest_value.weighted_value_index,
         
     | 
| 
      
 40 
     | 
    
         
            +
                      selected_index: nil # TODO
         
     | 
| 
      
 41 
     | 
    
         
            +
                    })
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def deepest_value
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @deepest_value ||= Prefab::ConfigValueUnwrapper.deepest_value(@value, @config.key, @context)
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'periodic_sync'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Prefab
         
     | 
| 
      
 6 
     | 
    
         
            +
              # This class aggregates the number of times each config is evaluated, and
         
     | 
| 
      
 7 
     | 
    
         
            +
              # details about how the config is evaluated This data is reported to the
         
     | 
| 
      
 8 
     | 
    
         
            +
              # server at a regular interval defined by `sync_interval`.
         
     | 
| 
      
 9 
     | 
    
         
            +
              class EvaluationSummaryAggregator
         
     | 
| 
      
 10 
     | 
    
         
            +
                include Prefab::PeriodicSync
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :data
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def initialize(client:, max_keys:, sync_interval:)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @client = client
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @max_keys = max_keys
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @name = 'evaluation_summary_aggregator'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  @data = Concurrent::Hash.new
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  start_periodic_sync(sync_interval)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def record(config_key:, config_type:, counter:)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return if @data.size >= @max_keys
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  key = [config_key, config_type]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @data[key] ||= Concurrent::Hash.new
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  @data[key][counter] ||= 0
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @data[key][counter] += 1
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                private
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def counter_proto(counter, count)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  PrefabProto::ConfigEvaluationCounter.new(
         
     | 
| 
      
 38 
     | 
    
         
            +
                    config_id: counter[:config_id],
         
     | 
| 
      
 39 
     | 
    
         
            +
                    selected_index: counter[:selected_index],
         
     | 
| 
      
 40 
     | 
    
         
            +
                    config_row_index: counter[:config_row_index],
         
     | 
| 
      
 41 
     | 
    
         
            +
                    conditional_value_index: counter[:conditional_value_index],
         
     | 
| 
      
 42 
     | 
    
         
            +
                    weighted_value_index: counter[:weighted_value_index],
         
     | 
| 
      
 43 
     | 
    
         
            +
                    selected_value: counter[:selected_value],
         
     | 
| 
      
 44 
     | 
    
         
            +
                    count: count
         
     | 
| 
      
 45 
     | 
    
         
            +
                  )
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def flush(to_ship, start_at_was)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  pool.post do
         
     | 
| 
      
 50 
     | 
    
         
            +
                    log_internal "Flushing #{to_ship.size} summaries"
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    summaries_proto = PrefabProto::ConfigEvaluationSummaries.new(
         
     | 
| 
      
 53 
     | 
    
         
            +
                      start: start_at_was,
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end: Prefab::TimeHelpers.now_in_ms,
         
     | 
| 
      
 55 
     | 
    
         
            +
                      summaries: summaries(to_ship)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    )
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    result = @client.post('/api/v1/telemetry', events(summaries_proto))
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    log_internal "Uploaded #{to_ship.size} summaries: #{result.status}"
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def events(summaries)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  event = PrefabProto::TelemetryEvent.new(summaries: summaries)
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  PrefabProto::TelemetryEvents.new(
         
     | 
| 
      
 68 
     | 
    
         
            +
                    instance_hash: @client.instance_hash,
         
     | 
| 
      
 69 
     | 
    
         
            +
                    events: [event]
         
     | 
| 
      
 70 
     | 
    
         
            +
                  )
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def summaries(data)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  data.map do |(config_key, config_type), counters|
         
     | 
| 
      
 75 
     | 
    
         
            +
                    counter_protos = counters.map { |counter, count| counter_proto(counter, count) }
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    PrefabProto::ConfigEvaluationSummary.new(
         
     | 
| 
      
 78 
     | 
    
         
            +
                      key: config_key,
         
     | 
| 
      
 79 
     | 
    
         
            +
                      type: config_type,
         
     | 
| 
      
 80 
     | 
    
         
            +
                      counters: counter_protos
         
     | 
| 
      
 81 
     | 
    
         
            +
                    )
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'periodic_sync'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Prefab
         
     | 
| 
      
 6 
     | 
    
         
            +
              # This class aggregates example contexts. It dedupes based on the
         
     | 
| 
      
 7 
     | 
    
         
            +
              # concatenation of the keys of the contexts.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              # It shouldn't send the same context more than once per hour.
         
     | 
| 
      
 10 
     | 
    
         
            +
              class ExampleContextsAggregator
         
     | 
| 
      
 11 
     | 
    
         
            +
                include Prefab::PeriodicSync
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :data, :cache
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                ONE_HOUR = 60 * 60
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize(client:, max_contexts:, sync_interval:)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @client = client
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @max_contexts = max_contexts
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @name = 'example_contexts_aggregator'
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  @data = Concurrent::Array.new
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @cache = Prefab::RateLimitCache.new(ONE_HOUR)
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  start_periodic_sync(sync_interval)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def record(contexts)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  key = contexts.grouped_key
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  return unless @data.size < @max_contexts && !@cache.fresh?(key)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  @cache.set(key)
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  @data.push(contexts)
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                private
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def on_prepare_data
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @cache.prune
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def flush(to_ship, _)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  pool.post do
         
     | 
| 
      
 46 
     | 
    
         
            +
                    log_internal "Flushing #{to_ship.size} examples"
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    result = @client.post('/api/v1/telemetry', events(to_ship))
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    log_internal "Uploaded #{to_ship.size} examples: #{result.status}"
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def example_contexts(to_ship)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  to_ship.map do |contexts|
         
     | 
| 
      
 56 
     | 
    
         
            +
                    PrefabProto::ExampleContext.new(
         
     | 
| 
      
 57 
     | 
    
         
            +
                      timestamp: contexts.seen_at * 1000,
         
     | 
| 
      
 58 
     | 
    
         
            +
                      contextSet: contexts.slim_proto
         
     | 
| 
      
 59 
     | 
    
         
            +
                    )
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def events(to_ship)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  event = PrefabProto::TelemetryEvent.new(
         
     | 
| 
      
 65 
     | 
    
         
            +
                    example_contexts: PrefabProto::ExampleContexts.new(
         
     | 
| 
      
 66 
     | 
    
         
            +
                      examples: example_contexts(to_ship)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    )
         
     | 
| 
      
 68 
     | 
    
         
            +
                  )
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  PrefabProto::TelemetryEvents.new(
         
     | 
| 
      
 71 
     | 
    
         
            +
                    instance_hash: @client.instance_hash,
         
     | 
| 
      
 72 
     | 
    
         
            +
                    events: [event]
         
     | 
| 
      
 73 
     | 
    
         
            +
                  )
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,4 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Prefab
         
     | 
| 
      
 4 
     | 
    
         
            +
              # This class implements exponential backoff with a maximum delay.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # This is the default sync interval for aggregators.
         
     | 
| 
       2 
7 
     | 
    
         
             
              class ExponentialBackoff
         
     | 
| 
       3 
8 
     | 
    
         
             
                def initialize(max_delay:, initial_delay: 2, multiplier: 2)
         
     | 
| 
       4 
9 
     | 
    
         
             
                  @initial_delay = initial_delay
         
     | 
| 
         @@ -11,8 +11,6 @@ module Prefab 
     | 
|
| 
       11 
11 
     | 
    
         
             
                end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
                def feature_is_on_for?(feature_name, properties)
         
     | 
| 
       14 
     | 
    
         
            -
                  @base_client.stats.increment('prefab.featureflag.on', tags: ["feature:#{feature_name}"])
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
14 
     | 
    
         
             
                  variant = @base_client.config_client.get(feature_name, false, properties)
         
     | 
| 
       17 
15 
     | 
    
         | 
| 
       18 
16 
     | 
    
         
             
                  is_on?(variant)
         
     |