launchdarkly-server-sdk 6.4.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/ldclient-rb/config.rb +102 -56
- data/lib/ldclient-rb/context.rb +487 -0
- data/lib/ldclient-rb/evaluation_detail.rb +20 -20
- data/lib/ldclient-rb/events.rb +77 -132
- data/lib/ldclient-rb/flags_state.rb +4 -4
- data/lib/ldclient-rb/impl/big_segments.rb +17 -17
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +379 -131
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +31 -34
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +6 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +12 -7
- data/lib/ldclient-rb/impl/event_types.rb +18 -30
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +29 -29
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +92 -12
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +232 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +52 -12
- data/lib/ldclient-rb/impl/repeating_task.rb +1 -1
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +2 -2
- data/lib/ldclient-rb/in_memory_store.rb +2 -2
- data/lib/ldclient-rb/integrations/consul.rb +1 -1
- data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
- data/lib/ldclient-rb/integrations/file_data.rb +3 -3
- data/lib/ldclient-rb/integrations/redis.rb +4 -4
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +218 -62
- data/lib/ldclient-rb/integrations/test_data.rb +16 -12
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +9 -9
- data/lib/ldclient-rb/interfaces.rb +14 -14
- data/lib/ldclient-rb/ldclient.rb +94 -144
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +2 -2
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +7 -8
- data/lib/ldclient-rb/util.rb +4 -19
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +2 -3
- metadata +34 -17
- data/lib/ldclient-rb/file_data_source.rb +0 -23
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
| @@ -163,30 +163,30 @@ module LaunchDarkly | |
| 163 163 | 
             
                  end
         | 
| 164 164 |  | 
| 165 165 | 
             
                  #
         | 
| 166 | 
            -
                  # Queries the store for a snapshot of the current segment state for a specific  | 
| 166 | 
            +
                  # Queries the store for a snapshot of the current segment state for a specific context.
         | 
| 167 167 | 
             
                  #
         | 
| 168 | 
            -
                  # The  | 
| 168 | 
            +
                  # The context_hash is a base64-encoded string produced by hashing the context key as defined by
         | 
| 169 169 | 
             
                  # the Big Segments specification; the store implementation does not need to know the details
         | 
| 170 170 | 
             
                  # of how this is done, because it deals only with already-hashed keys, but the string can be
         | 
| 171 171 | 
             
                  # assumed to only contain characters that are valid in base64.
         | 
| 172 172 | 
             
                  #
         | 
| 173 | 
            -
                  # The return value should be either a Hash, or nil if the  | 
| 173 | 
            +
                  # The return value should be either a Hash, or nil if the context is not referenced in any big
         | 
| 174 174 | 
             
                  # segments. Each key in the Hash is a "segment reference", which is how segments are
         | 
| 175 175 | 
             
                  # identified in Big Segment data. This string is not identical to the segment key-- the SDK
         | 
| 176 176 | 
             
                  # will add other information. The store implementation should not be concerned with the
         | 
| 177 | 
            -
                  # format of the string. Each value in the Hash is true if the  | 
| 178 | 
            -
                  # the segment, false if the  | 
| 177 | 
            +
                  # format of the string. Each value in the Hash is true if the context is explicitly included in
         | 
| 178 | 
            +
                  # the segment, false if the context is explicitly excluded from the segment-- and is not also
         | 
| 179 179 | 
             
                  # explicitly included (that is, if both an include and an exclude existed in the data, the
         | 
| 180 | 
            -
                  # include would take precedence). If the  | 
| 180 | 
            +
                  # include would take precedence). If the context's status in a particular segment is undefined,
         | 
| 181 181 | 
             
                  # there should be no key or value for that segment.
         | 
| 182 182 | 
             
                  #
         | 
| 183 183 | 
             
                  # This Hash may be cached by the SDK, so it should not be modified after it is created. It
         | 
| 184 184 | 
             
                  # is a snapshot of the segment membership state at one point in time.
         | 
| 185 185 | 
             
                  #
         | 
| 186 | 
            -
                  # @param  | 
| 187 | 
            -
                  # @return [Hash] true/false values for Big Segments that reference this  | 
| 186 | 
            +
                  # @param context_hash [String]
         | 
| 187 | 
            +
                  # @return [Hash] true/false values for Big Segments that reference this context
         | 
| 188 188 | 
             
                  #
         | 
| 189 | 
            -
                  def get_membership( | 
| 189 | 
            +
                  def get_membership(context_hash)
         | 
| 190 190 | 
             
                  end
         | 
| 191 191 |  | 
| 192 192 | 
             
                  #
         | 
| @@ -216,7 +216,7 @@ module LaunchDarkly | |
| 216 216 | 
             
                #
         | 
| 217 217 | 
             
                # Information about the status of a Big Segment store, provided by {BigSegmentStoreStatusProvider}.
         | 
| 218 218 | 
             
                #
         | 
| 219 | 
            -
                # Big Segments are a specific type of  | 
| 219 | 
            +
                # Big Segments are a specific type of segments. For more information, read the LaunchDarkly
         | 
| 220 220 | 
             
                # documentation: https://docs.launchdarkly.com/home/users/big-segments
         | 
| 221 221 | 
             
                #
         | 
| 222 222 | 
             
                class BigSegmentStoreStatus
         | 
| @@ -226,11 +226,11 @@ module LaunchDarkly | |
| 226 226 | 
             
                  end
         | 
| 227 227 |  | 
| 228 228 | 
             
                  # True if the Big Segment store is able to respond to queries, so that the SDK can evaluate
         | 
| 229 | 
            -
                  # whether a  | 
| 229 | 
            +
                  # whether a context is in a segment or not.
         | 
| 230 230 | 
             
                  #
         | 
| 231 231 | 
             
                  # If this property is false, the store is not able to make queries (for instance, it may not have
         | 
| 232 232 | 
             
                  # a valid database connection). In this case, the SDK will treat any reference to a Big Segment
         | 
| 233 | 
            -
                  # as if no  | 
| 233 | 
            +
                  # as if no contexts are included in that segment. Also, the {EvaluationReason} associated with
         | 
| 234 234 | 
             
                  # with any flag evaluation that references a Big Segment when the store is not available will
         | 
| 235 235 | 
             
                  # have a `big_segments_status` of `STORE_ERROR`.
         | 
| 236 236 | 
             
                  #
         | 
| @@ -259,14 +259,14 @@ module LaunchDarkly | |
| 259 259 | 
             
                #
         | 
| 260 260 | 
             
                # The Big Segment store is the component that receives information about Big Segments, normally
         | 
| 261 261 | 
             
                # from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type
         | 
| 262 | 
            -
                # of  | 
| 262 | 
            +
                # of segments. For more information, read the LaunchDarkly documentation:
         | 
| 263 263 | 
             
                # https://docs.launchdarkly.com/home/users/big-segments
         | 
| 264 264 | 
             
                #
         | 
| 265 265 | 
             
                # An implementation of this interface is returned by {LDClient#big_segment_store_status_provider}.
         | 
| 266 266 | 
             
                # Application code never needs to implement this interface.
         | 
| 267 267 | 
             
                #
         | 
| 268 268 | 
             
                # There are two ways to interact with the status. One is to simply get the current status; if its
         | 
| 269 | 
            -
                # `available` property is true, then the SDK is able to evaluate  | 
| 269 | 
            +
                # `available` property is true, then the SDK is able to evaluate context membership in Big Segments,
         | 
| 270 270 | 
             
                # and the `stale`` property indicates whether the data might be out of date.
         | 
| 271 271 | 
             
                #
         | 
| 272 272 | 
             
                # The other way is to subscribe to status change notifications. Applications may wish to know if
         | 
    
        data/lib/ldclient-rb/ldclient.rb
    CHANGED
    
    | @@ -59,7 +59,7 @@ module LaunchDarkly | |
| 59 59 |  | 
| 60 60 | 
             
                  get_flag = lambda { |key| @store.get(FEATURES, key) }
         | 
| 61 61 | 
             
                  get_segment = lambda { |key| @store.get(SEGMENTS, key) }
         | 
| 62 | 
            -
                  get_big_segments_membership = lambda { |key| @big_segment_store_manager. | 
| 62 | 
            +
                  get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_context_membership(key) }
         | 
| 63 63 | 
             
                  @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger)
         | 
| 64 64 |  | 
| 65 65 | 
             
                  if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
         | 
| @@ -120,26 +120,20 @@ module LaunchDarkly | |
| 120 120 | 
             
                end
         | 
| 121 121 |  | 
| 122 122 | 
             
                #
         | 
| 123 | 
            -
                #  | 
| 124 | 
            -
                # @param user [Hash] the user properties
         | 
| 125 | 
            -
                # @param default [Boolean] (false) the value to use if the flag cannot be evaluated
         | 
| 126 | 
            -
                # @return [Boolean] the flag value
         | 
| 127 | 
            -
                # @deprecated Use {#variation} instead.
         | 
| 128 | 
            -
                #
         | 
| 129 | 
            -
                def toggle?(key, user, default = false)
         | 
| 130 | 
            -
                  @config.logger.warn { "[LDClient] toggle? is deprecated. Use variation instead" }
         | 
| 131 | 
            -
                  variation(key, user, default)
         | 
| 132 | 
            -
                end
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                #
         | 
| 135 | 
            -
                # Creates a hash string that can be used by the JavaScript SDK to identify a user.
         | 
| 123 | 
            +
                # Creates a hash string that can be used by the JavaScript SDK to identify a context.
         | 
| 136 124 | 
             
                # For more information, see [Secure mode](https://docs.launchdarkly.com/sdk/features/secure-mode#ruby).
         | 
| 137 125 | 
             
                #
         | 
| 138 | 
            -
                # @param  | 
| 139 | 
            -
                # @return [String] a hash string
         | 
| 126 | 
            +
                # @param context [Hash, LDContext]
         | 
| 127 | 
            +
                # @return [String, nil] a hash string or nil if the provided context was invalid
         | 
| 140 128 | 
             
                #
         | 
| 141 | 
            -
                def secure_mode_hash( | 
| 142 | 
            -
                   | 
| 129 | 
            +
                def secure_mode_hash(context)
         | 
| 130 | 
            +
                  context = Impl::Context::make_context(context)
         | 
| 131 | 
            +
                  unless context.valid?
         | 
| 132 | 
            +
                    @config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
         | 
| 133 | 
            +
                    return nil
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  OpenSSL::HMAC.hexdigest("sha256", @sdk_key, context.fully_qualified_key)
         | 
| 143 137 | 
             
                end
         | 
| 144 138 |  | 
| 145 139 | 
             
                #
         | 
| @@ -165,44 +159,22 @@ module LaunchDarkly | |
| 165 159 | 
             
                end
         | 
| 166 160 |  | 
| 167 161 | 
             
                #
         | 
| 168 | 
            -
                # Determines the variation of a feature flag to present  | 
| 169 | 
            -
                #
         | 
| 170 | 
            -
                # At a minimum, the user hash should contain a `:key`, which should be the unique
         | 
| 171 | 
            -
                # identifier for your user (or, for an anonymous user, a session identifier or
         | 
| 172 | 
            -
                # cookie).
         | 
| 173 | 
            -
                #
         | 
| 174 | 
            -
                # Other supported user attributes include IP address, country code, and an arbitrary hash of
         | 
| 175 | 
            -
                # custom attributes. For more about the supported user properties and how they work in
         | 
| 176 | 
            -
                # LaunchDarkly, see [Targeting users](https://docs.launchdarkly.com/home/flags/targeting-users).
         | 
| 177 | 
            -
                #
         | 
| 178 | 
            -
                # The optional `:privateAttributeNames` user property allows you to specify a list of
         | 
| 179 | 
            -
                # attribute names that should not be sent back to LaunchDarkly.
         | 
| 180 | 
            -
                # [Private attributes](https://docs.launchdarkly.com/home/users/attributes#creating-private-user-attributes)
         | 
| 181 | 
            -
                # can also be configured globally in {Config}.
         | 
| 182 | 
            -
                #
         | 
| 183 | 
            -
                # @example Basic user hash
         | 
| 184 | 
            -
                #   {key: "my-user-id"}
         | 
| 185 | 
            -
                #
         | 
| 186 | 
            -
                # @example More complete user hash
         | 
| 187 | 
            -
                #   {key: "my-user-id", ip: "127.0.0.1", country: "US", custom: {customer_rank: 1000}}
         | 
| 188 | 
            -
                #
         | 
| 189 | 
            -
                # @example User with a private attribute
         | 
| 190 | 
            -
                #   {key: "my-user-id", email: "email@example.com", privateAttributeNames: ["email"]}
         | 
| 162 | 
            +
                # Determines the variation of a feature flag to present for a context.
         | 
| 191 163 | 
             
                #
         | 
| 192 164 | 
             
                # @param key [String] the unique feature key for the feature flag, as shown
         | 
| 193 165 | 
             
                #   on the LaunchDarkly dashboard
         | 
| 194 | 
            -
                # @param  | 
| 166 | 
            +
                # @param context [Hash, LDContext] a hash or LDContext instance describing the context requesting the flag
         | 
| 195 167 | 
             
                # @param default the default value of the flag; this is used if there is an error
         | 
| 196 168 | 
             
                #   condition making it impossible to find or evaluate the flag
         | 
| 197 169 | 
             
                #
         | 
| 198 | 
            -
                # @return the variation  | 
| 170 | 
            +
                # @return the variation for the provided context, or the default value if there's an an error
         | 
| 199 171 | 
             
                #
         | 
| 200 | 
            -
                def variation(key,  | 
| 201 | 
            -
                  evaluate_internal(key,  | 
| 172 | 
            +
                def variation(key, context, default)
         | 
| 173 | 
            +
                  evaluate_internal(key, context, default, false).value
         | 
| 202 174 | 
             
                end
         | 
| 203 175 |  | 
| 204 176 | 
             
                #
         | 
| 205 | 
            -
                # Determines the variation of a feature flag for a  | 
| 177 | 
            +
                # Determines the variation of a feature flag for a context, like {#variation}, but also
         | 
| 206 178 | 
             
                # provides additional information about how this value was calculated.
         | 
| 207 179 | 
             
                #
         | 
| 208 180 | 
             
                # The return value of `variation_detail` is an {EvaluationDetail} object, which has
         | 
| @@ -218,43 +190,48 @@ module LaunchDarkly | |
| 218 190 | 
             
                #
         | 
| 219 191 | 
             
                # @param key [String] the unique feature key for the feature flag, as shown
         | 
| 220 192 | 
             
                #   on the LaunchDarkly dashboard
         | 
| 221 | 
            -
                # @param  | 
| 193 | 
            +
                # @param context [Hash, LDContext] a hash or object describing the context requesting the flag,
         | 
| 222 194 | 
             
                # @param default the default value of the flag; this is used if there is an error
         | 
| 223 195 | 
             
                #   condition making it impossible to find or evaluate the flag
         | 
| 224 196 | 
             
                #
         | 
| 225 197 | 
             
                # @return [EvaluationDetail] an object describing the result
         | 
| 226 198 | 
             
                #
         | 
| 227 | 
            -
                def variation_detail(key,  | 
| 228 | 
            -
                  evaluate_internal(key,  | 
| 199 | 
            +
                def variation_detail(key, context, default)
         | 
| 200 | 
            +
                  evaluate_internal(key, context, default, true)
         | 
| 229 201 | 
             
                end
         | 
| 230 202 |  | 
| 231 203 | 
             
                #
         | 
| 232 | 
            -
                # Registers the  | 
| 233 | 
            -
                # properties, so that LaunchDarkly will know about that  | 
| 204 | 
            +
                # Registers the context. This method simply creates an analytics event containing the context
         | 
| 205 | 
            +
                # properties, so that LaunchDarkly will know about that context if it does not already.
         | 
| 234 206 | 
             
                #
         | 
| 235 | 
            -
                # Calling {#variation} or {#variation_detail} also sends the  | 
| 207 | 
            +
                # Calling {#variation} or {#variation_detail} also sends the context information to
         | 
| 236 208 | 
             
                # LaunchDarkly (if events are enabled), so you only need to use {#identify} if you
         | 
| 237 | 
            -
                # want to identify the  | 
| 209 | 
            +
                # want to identify the context without evaluating a flag.
         | 
| 238 210 | 
             
                #
         | 
| 239 211 | 
             
                # Note that event delivery is asynchronous, so the event may not actually be sent
         | 
| 240 212 | 
             
                # until later; see {#flush}.
         | 
| 241 213 | 
             
                #
         | 
| 242 | 
            -
                # @param  | 
| 243 | 
            -
                #   described in {#variation}
         | 
| 214 | 
            +
                # @param context [Hash, LDContext] a hash or object describing the context to register
         | 
| 244 215 | 
             
                # @return [void]
         | 
| 245 216 | 
             
                #
         | 
| 246 | 
            -
                def identify( | 
| 247 | 
            -
                   | 
| 248 | 
            -
             | 
| 217 | 
            +
                def identify(context)
         | 
| 218 | 
            +
                  context = LaunchDarkly::Impl::Context.make_context(context)
         | 
| 219 | 
            +
                  unless context.valid?
         | 
| 220 | 
            +
                    @config.logger.warn("Identify called with invalid context: #{context.error}")
         | 
| 221 | 
            +
                    return
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  if context.key == ""
         | 
| 225 | 
            +
                    @config.logger.warn("Identify called with empty key")
         | 
| 249 226 | 
             
                    return
         | 
| 250 227 | 
             
                  end
         | 
| 251 | 
            -
             | 
| 252 | 
            -
                  @event_processor.record_identify_event( | 
| 228 | 
            +
             | 
| 229 | 
            +
                  @event_processor.record_identify_event(context)
         | 
| 253 230 | 
             
                end
         | 
| 254 231 |  | 
| 255 232 | 
             
                #
         | 
| 256 | 
            -
                # Tracks that a  | 
| 257 | 
            -
                # containing the specified event name (key),  | 
| 233 | 
            +
                # Tracks that a context performed an event. This method creates a "custom" analytics event
         | 
| 234 | 
            +
                # containing the specified event name (key), context properties, and optional data.
         | 
| 258 235 | 
             
                #
         | 
| 259 236 | 
             
                # Note that event delivery is asynchronous, so the event may not actually be sent
         | 
| 260 237 | 
             
                # until later; see {#flush}.
         | 
| @@ -265,8 +242,7 @@ module LaunchDarkly | |
| 265 242 | 
             
                # for the latest status.
         | 
| 266 243 | 
             
                #
         | 
| 267 244 | 
             
                # @param event_name [String] The name of the event
         | 
| 268 | 
            -
                # @param  | 
| 269 | 
            -
                #   described in {#variation}
         | 
| 245 | 
            +
                # @param context [Hash, LDContext] a hash or object describing the context to track
         | 
| 270 246 | 
             
                # @param data [Hash] An optional hash containing any additional data associated with the event
         | 
| 271 247 | 
             
                # @param metric_value [Number] A numeric value used by the LaunchDarkly experimentation
         | 
| 272 248 | 
             
                #   feature in numeric custom metrics. Can be omitted if this event is used by only
         | 
| @@ -274,52 +250,22 @@ module LaunchDarkly | |
| 274 250 | 
             
                #   for Data Export.
         | 
| 275 251 | 
             
                # @return [void]
         | 
| 276 252 | 
             
                #
         | 
| 277 | 
            -
                def track(event_name,  | 
| 278 | 
            -
                   | 
| 279 | 
            -
             | 
| 280 | 
            -
                     | 
| 281 | 
            -
                  end
         | 
| 282 | 
            -
                  sanitize_user(user)
         | 
| 283 | 
            -
                  @event_processor.record_custom_event(user, event_name, data, metric_value)
         | 
| 284 | 
            -
                end
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                #
         | 
| 287 | 
            -
                # Associates a new and old user object for analytics purposes via an alias event.
         | 
| 288 | 
            -
                #
         | 
| 289 | 
            -
                # @param current_context [Hash] The current version of a user.
         | 
| 290 | 
            -
                # @param previous_context [Hash] The previous version of a user.
         | 
| 291 | 
            -
                # @return [void]
         | 
| 292 | 
            -
                #
         | 
| 293 | 
            -
                def alias(current_context, previous_context)
         | 
| 294 | 
            -
                  if !current_context || current_context[:key].nil? || !previous_context || previous_context[:key].nil?
         | 
| 295 | 
            -
                    @config.logger.warn("Alias called with nil user or nil user key!")
         | 
| 253 | 
            +
                def track(event_name, context, data = nil, metric_value = nil)
         | 
| 254 | 
            +
                  context = LaunchDarkly::Impl::Context.make_context(context)
         | 
| 255 | 
            +
                  unless context.valid?
         | 
| 256 | 
            +
                    @config.logger.warn("Track called with invalid context: #{context.error}")
         | 
| 296 257 | 
             
                    return
         | 
| 297 258 | 
             
                  end
         | 
| 298 | 
            -
                  sanitize_user(current_context)
         | 
| 299 | 
            -
                  sanitize_user(previous_context)
         | 
| 300 | 
            -
                  @event_processor.record_alias_event(current_context, previous_context)
         | 
| 301 | 
            -
                end
         | 
| 302 259 |  | 
| 303 | 
            -
             | 
| 304 | 
            -
                # Returns all feature flag values for the given user.
         | 
| 305 | 
            -
                #
         | 
| 306 | 
            -
                # @deprecated Please use {#all_flags_state} instead. Current versions of the
         | 
| 307 | 
            -
                #   client-side SDK will not generate analytics events correctly if you pass the
         | 
| 308 | 
            -
                #   result of `all_flags`.
         | 
| 309 | 
            -
                #
         | 
| 310 | 
            -
                # @param user [Hash] The end user requesting the feature flags
         | 
| 311 | 
            -
                # @return [Hash] a hash of feature flag keys to values
         | 
| 312 | 
            -
                #
         | 
| 313 | 
            -
                def all_flags(user)
         | 
| 314 | 
            -
                  all_flags_state(user).values_map
         | 
| 260 | 
            +
                  @event_processor.record_custom_event(context, event_name, data, metric_value)
         | 
| 315 261 | 
             
                end
         | 
| 316 262 |  | 
| 317 263 | 
             
                #
         | 
| 318 | 
            -
                # Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given  | 
| 264 | 
            +
                # Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
         | 
| 319 265 | 
             
                # including the flag values and also metadata that can be used on the front end. This method does not
         | 
| 320 266 | 
             
                # send analytics events back to LaunchDarkly.
         | 
| 321 267 | 
             
                #
         | 
| 322 | 
            -
                # @param  | 
| 268 | 
            +
                # @param context [Hash, LDContext] a hash or object describing the context requesting the flags,
         | 
| 323 269 | 
             
                # @param options [Hash] Optional parameters to control how the state is generated
         | 
| 324 270 | 
             
                # @option options [Boolean] :client_side_only (false) True if only flags marked for use with the
         | 
| 325 271 | 
             
                #   client-side SDK should be included in the state. By default, all flags are included.
         | 
| @@ -331,10 +277,10 @@ module LaunchDarkly | |
| 331 277 | 
             
                #   of the JSON data if you are passing the flag state to the front end.
         | 
| 332 278 | 
             
                # @return [FeatureFlagsState] a {FeatureFlagsState} object which can be serialized to JSON
         | 
| 333 279 | 
             
                #
         | 
| 334 | 
            -
                def all_flags_state( | 
| 280 | 
            +
                def all_flags_state(context, options={})
         | 
| 335 281 | 
             
                  return FeatureFlagsState.new(false) if @config.offline?
         | 
| 336 282 |  | 
| 337 | 
            -
                   | 
| 283 | 
            +
                  unless initialized?
         | 
| 338 284 | 
             
                    if @store.initialized?
         | 
| 339 285 | 
             
                        @config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
         | 
| 340 286 | 
             
                    else
         | 
| @@ -343,8 +289,9 @@ module LaunchDarkly | |
| 343 289 | 
             
                    end
         | 
| 344 290 | 
             
                  end
         | 
| 345 291 |  | 
| 346 | 
            -
                   | 
| 347 | 
            -
             | 
| 292 | 
            +
                  context = Impl::Context::make_context(context)
         | 
| 293 | 
            +
                  unless context.valid?
         | 
| 294 | 
            +
                    @config.logger.error { "[LDClient] Context was invalid for all_flags_state (#{context.error})" }
         | 
| 348 295 | 
             
                    return FeatureFlagsState.new(false)
         | 
| 349 296 | 
             
                  end
         | 
| 350 297 |  | 
| @@ -364,13 +311,13 @@ module LaunchDarkly | |
| 364 311 | 
             
                      next
         | 
| 365 312 | 
             
                    end
         | 
| 366 313 | 
             
                    begin
         | 
| 367 | 
            -
                      detail = @evaluator.evaluate(f,  | 
| 314 | 
            +
                      detail = @evaluator.evaluate(f, context).detail
         | 
| 368 315 | 
             
                    rescue => exn
         | 
| 369 316 | 
             
                      detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
         | 
| 370 317 | 
             
                      Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
         | 
| 371 318 | 
             
                    end
         | 
| 372 319 |  | 
| 373 | 
            -
                    requires_experiment_data =  | 
| 320 | 
            +
                    requires_experiment_data = experiment?(f, detail.reason)
         | 
| 374 321 | 
             
                    flag_state = {
         | 
| 375 322 | 
             
                      key: f[:key],
         | 
| 376 323 | 
             
                      value: detail.value,
         | 
| @@ -425,32 +372,34 @@ module LaunchDarkly | |
| 425 372 | 
             
                  end
         | 
| 426 373 | 
             
                end
         | 
| 427 374 |  | 
| 375 | 
            +
                # @param context [Hash, LDContext]
         | 
| 428 376 | 
             
                # @return [EvaluationDetail]
         | 
| 429 | 
            -
                def evaluate_internal(key,  | 
| 377 | 
            +
                def evaluate_internal(key, context, default, with_reasons)
         | 
| 430 378 | 
             
                  if @config.offline?
         | 
| 431 379 | 
             
                    return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
         | 
| 432 380 | 
             
                  end
         | 
| 433 381 |  | 
| 434 | 
            -
                   | 
| 435 | 
            -
                    @config.logger.error { "[LDClient] Must specify  | 
| 382 | 
            +
                  if context.nil?
         | 
| 383 | 
            +
                    @config.logger.error { "[LDClient] Must specify context" }
         | 
| 436 384 | 
             
                    detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
         | 
| 437 385 | 
             
                    return detail
         | 
| 438 386 | 
             
                  end
         | 
| 439 387 |  | 
| 440 | 
            -
                   | 
| 441 | 
            -
             | 
| 388 | 
            +
                  context = Impl::Context::make_context(context)
         | 
| 389 | 
            +
                  unless context.valid?
         | 
| 390 | 
            +
                    @config.logger.error { "[LDClient] Context was invalid for flag evaluation (#{context.error}); returning default value" }
         | 
| 442 391 | 
             
                    detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
         | 
| 443 392 | 
             
                    return detail
         | 
| 444 393 | 
             
                  end
         | 
| 445 394 |  | 
| 446 | 
            -
                   | 
| 395 | 
            +
                  unless initialized?
         | 
| 447 396 | 
             
                    if @store.initialized?
         | 
| 448 397 | 
             
                      @config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
         | 
| 449 398 | 
             
                    else
         | 
| 450 399 | 
             
                      @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
         | 
| 451 400 | 
             
                      detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
         | 
| 452 | 
            -
                      record_unknown_flag_eval(key,  | 
| 453 | 
            -
                      return | 
| 401 | 
            +
                      record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
         | 
| 402 | 
            +
                      return detail
         | 
| 454 403 | 
             
                    end
         | 
| 455 404 | 
             
                  end
         | 
| 456 405 |  | 
| @@ -459,35 +408,35 @@ module LaunchDarkly | |
| 459 408 | 
             
                  if feature.nil?
         | 
| 460 409 | 
             
                    @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
         | 
| 461 410 | 
             
                    detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
         | 
| 462 | 
            -
                    record_unknown_flag_eval(key,  | 
| 411 | 
            +
                    record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
         | 
| 463 412 | 
             
                    return detail
         | 
| 464 413 | 
             
                  end
         | 
| 465 414 |  | 
| 466 415 | 
             
                  begin
         | 
| 467 | 
            -
                    res = @evaluator.evaluate(feature,  | 
| 468 | 
            -
                     | 
| 416 | 
            +
                    res = @evaluator.evaluate(feature, context)
         | 
| 417 | 
            +
                    unless res.prereq_evals.nil?
         | 
| 469 418 | 
             
                      res.prereq_evals.each do |prereq_eval|
         | 
| 470 | 
            -
                        record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag,  | 
| 419 | 
            +
                        record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag, context, prereq_eval.detail, with_reasons)
         | 
| 471 420 | 
             
                      end
         | 
| 472 421 | 
             
                    end
         | 
| 473 422 | 
             
                    detail = res.detail
         | 
| 474 423 | 
             
                    if detail.default_value?
         | 
| 475 424 | 
             
                      detail = EvaluationDetail.new(default, nil, detail.reason)
         | 
| 476 425 | 
             
                    end
         | 
| 477 | 
            -
                    record_flag_eval(feature,  | 
| 478 | 
            -
                     | 
| 426 | 
            +
                    record_flag_eval(feature, context, detail, default, with_reasons)
         | 
| 427 | 
            +
                    detail
         | 
| 479 428 | 
             
                  rescue => exn
         | 
| 480 429 | 
             
                    Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
         | 
| 481 430 | 
             
                    detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
         | 
| 482 | 
            -
                    record_flag_eval_error(feature,  | 
| 483 | 
            -
                     | 
| 431 | 
            +
                    record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
         | 
| 432 | 
            +
                    detail
         | 
| 484 433 | 
             
                  end
         | 
| 485 434 | 
             
                end
         | 
| 486 435 |  | 
| 487 | 
            -
                private def record_flag_eval(flag,  | 
| 488 | 
            -
                  add_experiment_data =  | 
| 436 | 
            +
                private def record_flag_eval(flag, context, detail, default, with_reasons)
         | 
| 437 | 
            +
                  add_experiment_data = experiment?(flag, detail.reason)
         | 
| 489 438 | 
             
                  @event_processor.record_eval_event(
         | 
| 490 | 
            -
                     | 
| 439 | 
            +
                    context,
         | 
| 491 440 | 
             
                    flag[:key],
         | 
| 492 441 | 
             
                    flag[:version],
         | 
| 493 442 | 
             
                    detail.variation_index,
         | 
| @@ -499,11 +448,11 @@ module LaunchDarkly | |
| 499 448 | 
             
                    nil
         | 
| 500 449 | 
             
                  )
         | 
| 501 450 | 
             
                end
         | 
| 502 | 
            -
             | 
| 503 | 
            -
                private def record_prereq_flag_eval(prereq_flag, prereq_of_flag,  | 
| 504 | 
            -
                  add_experiment_data =  | 
| 451 | 
            +
             | 
| 452 | 
            +
                private def record_prereq_flag_eval(prereq_flag, prereq_of_flag, context, detail, with_reasons)
         | 
| 453 | 
            +
                  add_experiment_data = experiment?(prereq_flag, detail.reason)
         | 
| 505 454 | 
             
                  @event_processor.record_eval_event(
         | 
| 506 | 
            -
                     | 
| 455 | 
            +
                    context,
         | 
| 507 456 | 
             
                    prereq_flag[:key],
         | 
| 508 457 | 
             
                    prereq_flag[:version],
         | 
| 509 458 | 
             
                    detail.variation_index,
         | 
| @@ -515,19 +464,26 @@ module LaunchDarkly | |
| 515 464 | 
             
                    prereq_of_flag[:key]
         | 
| 516 465 | 
             
                  )
         | 
| 517 466 | 
             
                end
         | 
| 518 | 
            -
             | 
| 519 | 
            -
                private def record_flag_eval_error(flag,  | 
| 520 | 
            -
                  @event_processor.record_eval_event( | 
| 467 | 
            +
             | 
| 468 | 
            +
                private def record_flag_eval_error(flag, context, default, reason, with_reasons)
         | 
| 469 | 
            +
                  @event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
         | 
| 521 470 | 
             
                    flag[:trackEvents], flag[:debugEventsUntilDate], nil)
         | 
| 522 471 | 
             
                end
         | 
| 523 472 |  | 
| 524 | 
            -
                 | 
| 525 | 
            -
             | 
| 473 | 
            +
                #
         | 
| 474 | 
            +
                # @param flag_key [String]
         | 
| 475 | 
            +
                # @param context [LaunchDarkly::LDContext]
         | 
| 476 | 
            +
                # @param default [any]
         | 
| 477 | 
            +
                # @param reason [LaunchDarkly::EvaluationReason]
         | 
| 478 | 
            +
                # @param with_reasons [Boolean]
         | 
| 479 | 
            +
                #
         | 
| 480 | 
            +
                private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
         | 
| 481 | 
            +
                  @event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
         | 
| 526 482 | 
             
                    false, nil, nil)
         | 
| 527 483 | 
             
                end
         | 
| 528 484 |  | 
| 529 | 
            -
                private def  | 
| 530 | 
            -
                  return false  | 
| 485 | 
            +
                private def experiment?(flag, reason)
         | 
| 486 | 
            +
                  return false unless reason
         | 
| 531 487 |  | 
| 532 488 | 
             
                  if reason.in_experiment
         | 
| 533 489 | 
             
                    return true
         | 
| @@ -536,7 +492,7 @@ module LaunchDarkly | |
| 536 492 | 
             
                  case reason[:kind]
         | 
| 537 493 | 
             
                  when 'RULE_MATCH'
         | 
| 538 494 | 
             
                    index = reason[:ruleIndex]
         | 
| 539 | 
            -
                     | 
| 495 | 
            +
                    unless index.nil?
         | 
| 540 496 | 
             
                      rules = flag[:rules] || []
         | 
| 541 497 | 
             
                      return index >= 0 && index < rules.length && rules[index][:trackEvents]
         | 
| 542 498 | 
             
                    end
         | 
| @@ -545,12 +501,6 @@ module LaunchDarkly | |
| 545 501 | 
             
                  end
         | 
| 546 502 | 
             
                  false
         | 
| 547 503 | 
             
                end
         | 
| 548 | 
            -
             | 
| 549 | 
            -
                private def sanitize_user(user)
         | 
| 550 | 
            -
                  if user[:key]
         | 
| 551 | 
            -
                    user[:key] = user[:key].to_s
         | 
| 552 | 
            -
                  end
         | 
| 553 | 
            -
                end
         | 
| 554 504 | 
             
              end
         | 
| 555 505 |  | 
| 556 506 | 
             
              #
         | 
| @@ -17,7 +17,7 @@ module LaunchDarkly | |
| 17 17 | 
             
                # Attempts to submit a job, but only if a worker is available. Unlike the regular post method,
         | 
| 18 18 | 
             
                # this returns a value: true if the job was submitted, false if all workers are busy.
         | 
| 19 19 | 
             
                def post
         | 
| 20 | 
            -
                   | 
| 20 | 
            +
                  unless @semaphore.try_acquire(1)
         | 
| 21 21 | 
             
                    return
         | 
| 22 22 | 
             
                  end
         | 
| 23 23 | 
             
                  @pool.post do
         | 
    
        data/lib/ldclient-rb/polling.rb
    CHANGED
    
    | @@ -43,8 +43,8 @@ module LaunchDarkly | |
| 43 43 | 
             
                    end
         | 
| 44 44 | 
             
                  rescue UnexpectedResponseError => e
         | 
| 45 45 | 
             
                    message = Util.http_error_message(e.status, "polling request", "will retry")
         | 
| 46 | 
            -
                    @config.logger.error { "[LDClient] #{message}" } | 
| 47 | 
            -
                     | 
| 46 | 
            +
                    @config.logger.error { "[LDClient] #{message}" }
         | 
| 47 | 
            +
                    unless Util.http_error_recoverable?(e.status)
         | 
| 48 48 | 
             
                      @ready.set  # if client was waiting on us, make it stop waiting - has no effect if already set
         | 
| 49 49 | 
             
                      stop
         | 
| 50 50 | 
             
                    end
         |