Skip to content

Commit aa266b6

Browse files
LaunchDarklyReleaseBothroederldeli-darklybwoskow-ldLaunchDarklyCI
authored
prepare 6.4.0 release (#205)
* Hr/azure3 (#103) * Add Consul and Redis services to Windows. * Enable Consul and Redis testing * add dynamo (#104) * add experimentation event overrides for rules and fallthrough * warn & don't send event if identify or track has no valid user * include user in prereq flag events * rm unnecessary logic * more factory methods * update readme to refer to docs * add Ruby 2.6.2 to CI * fix missing require for net/http * stringify built-in user attributes in events, and secondary key for evals * make const names consistent * support metric value with track() * update method description * applying markdown templates and updating repository url references * Cleaning up markdown files * allow skipping database tests * Updating the package name (#115) * update package name * missed one * revert module entry point name change * bump ld-eventsource version for stream logging fix * use YAML.safe_load * add unit test and temporarily revert fix to demonstrate failure * restore fix * add comment about not using FileDataSource in production * drop events if inbox is full * update doc comment for track with metric_value * don't let user fall outside of last bucket in rollout * refactor evaluation logic and move it out of the main namespace * comments * fix type coercion behavior * make type coercion behavior consistent with earlier versions for now * whitespace * break up Evaluator tests further * make EvaluationReason an immutable class * FrozenError doesn't exist in older Ruby, use more general RuntimeError * precompute evaluation reasons when we receive a flag * rm unused * rename FeatureStore to DataStore * remove references to UpdateProcessor (now DataSource) * add event payload ID header * (6.0) drop support for old Ruby versions * add Ruby version constraint to gemspec * remove Rake dependency * update ld-eventsource to 1.0.2 which doesn't have Rake dependency * implement diagnostic events in Ruby (#130) * update ruby-eventsource to 1.0.3 for backoff bug * fix incorrect initialization of EventProcessor * remove install-time openssl check that breaks if you don't have rake * treat comparison with wrong data type as a non-match, not an exception (#134) * fail fast for nil SDK key when appropriate * tolerate nil value for user.custom (#137) * Only shutdown the Redis pool if it is owned by the SDK (#158) * Only shutdown a Redis pool created by SDK * Make pool shutdown behavior an option * improve doc comment * remove support for indirect/patch and indirect/put (#138) * update to json 2.3.1 (#139) * update json dep to 2.3.x to fix CVE * add publication of API docs on GitHub Pages (#143) * try fixing release metadata * update the default base url (#144) * revert renames of feature_store & update_processor * [ch92483] Use http gem and add socket factory support (#142) * update dependencies and add CI for ruby 3 (#141) * reference eventsource 2.0 in gemspec * add 5.x releasable branch for releaser * use Ruby 2.6.6 in releases * Removed the guides link * [ch99757] add alias method (#147) * don't send event for nil user evaluation * remove lockfile (#148) * rm redundant nil check * Experiment Allocation Changes (#150) * WIP - from sam's pairing session * starting sdk changes * adding tests and making sure everything works * adding more tests * removing the singleton for fallthrough * Revert "removing the singleton for fallthrough" This reverts commit dff7adbb809ecc63118d0fbff9742a88a039c679. * taking a different approach to keep things immutable * adding tests for untracked * remove unnecessary comment * making sure to return two values in all code paths Co-authored-by: pellyg-ld <gpelly@launchdarkly.com> * Use camelCase for JSON property names (#151) The in_experiment attribute was added to reasons as part of #150 but it doesn't appear to be received in events. I think that's because it's sending it in JSON as "in_experiment" rather than "inExperiment" as we expect to parse it. * fixing ruby logic causing ih failures (#152) * fixing ruby logic * adding missing spec * Apply suggestions from code review Co-authored-by: Sam Stokes <sstokes@launchdarkly.com> * pr tweaks * making spec language consistent Co-authored-by: Sam Stokes <sstokes@launchdarkly.com> * add log warning for missing user key (#153) * add log warnings for nil/empty user key * rm warning for empty string key * fix test * diagnostic events should respect HTTPS_PROXY (#154) * minor test simplification (#155) * allow higher minor versions of json and http gems * allow v5.x of http gem (#157) * use Bundler 2.2.10 + modernize CI config (#158) * enable verbose rspec output * fix socket factory tests * restore log suppression * Replacing deprecated circleci image usage (#159) * use Releaser v2 config (#161) * Updates docs URLs * Update lib/ldclient-rb/ldclient.rb Co-authored-by: Louis Chan <91093020+louis-launchdarkly@users.noreply.github.com> * remove reliance on git in gemspec (#163) * use ruby-eventsource 2.1.1 for fix of sc-123850 and sc-125504 (#164) * use ruby-eventsource 2.1.1 for fix of sc-123850 and sc-125504 * comment phrasing * Start work on flag builder. * Add user targeting and rule builder * Add datasource implementation * Convert the current_flags hash to use symbols instead of strings as keys * Fix typo on FlagRuleBuilder copy constructor * minor refactoring of impl; Added use of new Clause struct instead of Hash in FlagRuleBuilder; Moved TestData.factory out of Impl namespace and renamed Impl to TestDataImpl * Add the doc comments * (big segments 1) add public config/interface/reason types (#167) * Cleanup docstrings to be YARD docs * Added Util.is_bool helper function to clean up the check for whether an object is a boolean; Removed the DeepCopyHash/DeepCopyArray objects in favor of deep_copy_hash and deep_copy_array functions * Move public classes out of Impl namespace. Most of it is in public namespace except for the data source now. * Move require of concurrent/atomics to the correct module * (big segments 2) implement Big Segments evaluation & status APIs (#168) * improve CONTRIBUTING.md with notes on code organization * add note about doc comments * Cleanup YARD warnings and cleanup docs * Address PR feedback: Move is_bool back to Impl namespace to avoid confusion; Remove unnecessary nil check on variations in build function; fixup comments * (big segments 3) implement Redis & DynamoDB big segment stores (#169) * add missing import * fix stale calculation * fix big segments user hash algorithm to use SHA256 * improve & refactor client/evaluation tests * more cleanup/DRY * add use_preconfigured_flag and use_preconfigured_segment to TestData (#173) * always cache big segment query result even if it's nil * comments * add test for cache expiration * use TestData in our own tests (#174) * use TestData in our own tests * fix test * replace LaunchDarkly::FileDataSource with LaunchDarkly::Integrations::FileData * update ruby-eventsource version for recent SSE fixes * Bump bundler version (#184) * Add ability to to set initial reconnect delay (#183) * Treat secondary as a built-in attribute (#180) * all_flags_state is invalid if store isn't initialized (#182) * identify should not emit events if user key is "" (#181) * Account for traffic allocation on all flags (#185) * Add contract tests (#178) * Fix string interpolation in log message (#187) * Default opts to empty hash when creating persistent feature store (#186) * Remove Hakiri badge from README (#188) Hakiri was sunset on January 31st, 2022 at which time our badge stopped working. * detect http/https proxy env vars when creating HTTP clients * rever accidental change * fix nil safety in test service config * master -> main (#190) * master -> main * update ruby-eventsource version for parsing efficiency fix * miscellaneous optimizations for event processing (#193) * Add polling support for contract test service (#198) * Add windows tests in circleci (#199) At some point in the past, we were experimenting with using Azure to verify Window builds. Now that CircleCI supports Windows, we should keep everything on a single CI provider. * Add application info support (#194) * reuse EvaluationDetail instances by precomputing results Co-authored-by: hroederld <46500128+hroederld@users.noreply.github.com> Co-authored-by: Eli Bishop <eli@launchdarkly.com> Co-authored-by: Ben Woskow <bwoskow@launchdarkly.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com> Co-authored-by: Jacob Smith <jacob@jacobsmith.io> Co-authored-by: Elliot <35050275+Apache-HB@users.noreply.github.com> Co-authored-by: hroederld <hroeder@launchdarkly.com> Co-authored-by: Kerrie Martinez <kyee@launchdarkly.com> Co-authored-by: pellyg-ld <gpelly@launchdarkly.com> Co-authored-by: Sam Stokes <sstokes@launchdarkly.com> Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com> Co-authored-by: Ember Stevens <ember.stevens@launchdarkly.com> Co-authored-by: ember-stevens <79482775+ember-stevens@users.noreply.github.com> Co-authored-by: Louis Chan <91093020+louis-launchdarkly@users.noreply.github.com> Co-authored-by: Matthew M. Keeler <keelerm84@gmail.com> Co-authored-by: Ben Levy <benjaminlevy007@gmail.com> Co-authored-by: Ben Levy <ben@foxhound.systems> Co-authored-by: Matthew M. Keeler <mkeeler@launchdarkly.com> Co-authored-by: Louis Chan <lchan@launchdarkly.com>
1 parent d2118b3 commit aa266b6

25 files changed

+1183
-605
lines changed

.circleci/config.yml

+65
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
version: 2.1
22

3+
orbs:
4+
win: circleci/windows@4.1.1
5+
36
workflows:
47
version: 2
58
test:
69
jobs:
10+
- build-test-windows
711
- build-test-linux:
812
name: Ruby 2.5
913
docker-image: cimg/ruby:2.5
@@ -22,6 +26,67 @@ workflows:
2226
jruby: true
2327

2428
jobs:
29+
build-test-windows:
30+
executor:
31+
name: win/default
32+
33+
steps:
34+
- checkout
35+
36+
- run:
37+
name: "Setup DynamoDB"
38+
command: |
39+
iwr -outf dynamo.zip https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.zip
40+
mkdir dynamo
41+
Expand-Archive -Path dynamo.zip -DestinationPath dynamo
42+
- run:
43+
name: "Run DynamoDB"
44+
background: true
45+
working_directory: dynamo
46+
command: javaw -D"java.library.path=./DynamoDBLocal_lib" -jar DynamoDBLocal.jar
47+
48+
- run:
49+
name: "Setup Consul"
50+
command: |
51+
iwr -outf consul.zip https://releases.hashicorp.com/consul/1.4.2/consul_1.4.2_windows_amd64.zip
52+
mkdir consul
53+
Expand-Archive -Path consul.zip -DestinationPath consul
54+
sc.exe create "Consul" binPath="C:/Users/circleci/project/consul/consul.exe agent -dev"
55+
- run:
56+
name: "Run Consul"
57+
background: true
58+
working_directory: consul
59+
command: sc.exe start "Consul"
60+
61+
- run:
62+
name: "Setup Redis"
63+
command: |
64+
iwr -outf redis.zip https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.zip
65+
mkdir redis
66+
Expand-Archive -Path redis.zip -DestinationPath redis
67+
cd redis
68+
./redis-server --service-install
69+
- run:
70+
name: "Run Redis"
71+
background: true
72+
working_directory: redis
73+
command: |
74+
./redis-server --service-start
75+
76+
- run: ruby -v
77+
- run: choco install msys2 --allow-downgrade -y --version 20200903.0.0
78+
- run: ridk.cmd exec pacman -S --noconfirm --needed base-devel mingw-w64-x86_64-toolchain
79+
80+
- run: gem install bundler -v 2.2.33
81+
- run: bundle _2.2.33_ install
82+
- run: mkdir /tmp/circle-artifacts
83+
- run: bundle _2.2.33_ exec rspec --format documentation --format RspecJunitFormatter -o /tmp/circle-artifacts/rspec.xml spec
84+
85+
- store_test_results:
86+
path: /tmp/circle-artifacts
87+
- store_artifacts:
88+
path: /tmp/circle-artifacts
89+
2590
build-test-linux:
2691
parameters:
2792
docker-image:

azure-pipelines.yml

-51
This file was deleted.

contract-tests/client_entity.rb

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def initialize(log, config)
1414
streaming = config[:streaming]
1515
opts[:stream_uri] = streaming[:baseUri] if !streaming[:baseUri].nil?
1616
opts[:initial_reconnect_delay] = streaming[:initialRetryDelayMs] / 1_000.0 if !streaming[:initialRetryDelayMs].nil?
17+
elsif config[:polling]
18+
polling = config[:polling]
19+
opts[:stream] = false
20+
opts[:base_uri] = polling[:baseUri] if !polling[:baseUri].nil?
21+
opts[:poll_interval] = polling[:pollIntervalMs] / 1_000.0 if !polling[:pollIntervalMs].nil?
1722
end
1823

1924
if config[:events]
@@ -29,6 +34,13 @@ def initialize(log, config)
2934
opts[:send_events] = false
3035
end
3136

37+
if config[:tags]
38+
opts[:application] = {
39+
:id => config[:tags][:applicationId],
40+
:version => config[:tags][:applicationVersion],
41+
}
42+
end
43+
3244
startWaitTimeMs = config[:startWaitTimeMs] || 5_000
3345

3446
@client = LaunchDarkly::LDClient.new(

contract-tests/service.rb

+2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
{
2626
capabilities: [
2727
'server-side',
28+
'server-side-polling',
2829
'all-flags-with-reasons',
2930
'all-flags-client-side-only',
3031
'all-flags-details-only-for-tracked-flags',
32+
'tags',
3133
]
3234
}.to_json
3335
end

lib/ldclient-rb/config.rb

+20
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Config
4444
# @option opts [String] :wrapper_version See {#wrapper_version}.
4545
# @option opts [#open] :socket_factory See {#socket_factory}.
4646
# @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
47+
# @option opts [Hash] :application See {#application}
4748
#
4849
def initialize(opts = {})
4950
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -77,6 +78,7 @@ def initialize(opts = {})
7778
@wrapper_version = opts[:wrapper_version]
7879
@socket_factory = opts[:socket_factory]
7980
@big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
81+
@application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
8082
end
8183

8284
#
@@ -284,6 +286,24 @@ def offline?
284286
#
285287
attr_reader :big_segments
286288

289+
#
290+
# An object that allows configuration of application metadata.
291+
#
292+
# Application metadata may be used in LaunchDarkly analytics or other product features, but does not affect feature flag evaluations.
293+
#
294+
# If you want to set non-default values for any of these fields, provide the appropriately configured hash to the {Config} object.
295+
#
296+
# @example Configuring application information
297+
# opts[:application] = {
298+
# id: "MY APPLICATION ID",
299+
# version: "MY APPLICATION VERSION"
300+
# }
301+
# config = LDConfig.new(opts)
302+
#
303+
# @return [Hash]
304+
#
305+
attr_reader :application
306+
287307
# @deprecated This is replaced by {#data_source}.
288308
attr_reader :update_processor
289309

lib/ldclient-rb/impl/evaluator.rb

+25-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require "ldclient-rb/evaluation_detail"
22
require "ldclient-rb/impl/evaluator_bucketing"
3+
require "ldclient-rb/impl/evaluator_helpers"
34
require "ldclient-rb/impl/evaluator_operators"
45

56
module LaunchDarkly
@@ -87,19 +88,17 @@ def self.make_big_segment_ref(segment) # method is visible for testing
8788

8889
def eval_internal(flag, user, state)
8990
if !flag[:on]
90-
return get_off_value(flag, EvaluationReason::off)
91+
return EvaluatorHelpers.off_result(flag)
9192
end
9293

93-
prereq_failure_reason = check_prerequisites(flag, user, state)
94-
if !prereq_failure_reason.nil?
95-
return get_off_value(flag, prereq_failure_reason)
96-
end
94+
prereq_failure_result = check_prerequisites(flag, user, state)
95+
return prereq_failure_result if !prereq_failure_result.nil?
9796

9897
# Check user target matches
9998
(flag[:targets] || []).each do |target|
10099
(target[:values] || []).each do |value|
101100
if value == user[:key]
102-
return get_variation(flag, target[:variation], EvaluationReason::target_match)
101+
return EvaluatorHelpers.target_match_result(target, flag)
103102
end
104103
end
105104
end
@@ -111,13 +110,15 @@ def eval_internal(flag, user, state)
111110
if rule_match_user(rule, user, state)
112111
reason = rule[:_reason] # try to use cached reason for this rule
113112
reason = EvaluationReason::rule_match(i, rule[:id]) if reason.nil?
114-
return get_value_for_variation_or_rollout(flag, rule, user, reason)
113+
return get_value_for_variation_or_rollout(flag, rule, user, reason,
114+
EvaluatorHelpers.rule_precomputed_results(rule))
115115
end
116116
end
117117

118118
# Check the fallthrough rule
119119
if !flag[:fallthrough].nil?
120-
return get_value_for_variation_or_rollout(flag, flag[:fallthrough], user, EvaluationReason::fallthrough)
120+
return get_value_for_variation_or_rollout(flag, flag[:fallthrough], user, EvaluationReason::fallthrough,
121+
EvaluatorHelpers.fallthrough_precomputed_results(flag))
121122
end
122123

123124
return EvaluationDetail.new(nil, nil, EvaluationReason::fallthrough)
@@ -149,8 +150,7 @@ def check_prerequisites(flag, user, state)
149150
end
150151
end
151152
if !prereq_ok
152-
reason = prerequisite[:_reason] # try to use cached reason
153-
return reason.nil? ? EvaluationReason::prerequisite_failed(prereq_key) : reason
153+
return EvaluatorHelpers.prerequisite_failed_result(prerequisite, flag)
154154
end
155155
end
156156
nil
@@ -253,35 +253,26 @@ def segment_rule_match_user(rule, user, segment_key, salt)
253253
end
254254

255255
private
256-
257-
def get_variation(flag, index, reason)
258-
if index < 0 || index >= flag[:variations].length
259-
@logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index")
260-
return Evaluator.error_result(EvaluationReason::ERROR_MALFORMED_FLAG)
261-
end
262-
EvaluationDetail.new(flag[:variations][index], index, reason)
263-
end
264-
265-
def get_off_value(flag, reason)
266-
if flag[:offVariation].nil? # off variation unspecified - return default value
267-
return EvaluationDetail.new(nil, nil, reason)
268-
end
269-
get_variation(flag, flag[:offVariation], reason)
270-
end
271-
272-
def get_value_for_variation_or_rollout(flag, vr, user, reason)
256+
257+
def get_value_for_variation_or_rollout(flag, vr, user, reason, precomputed_results)
273258
index, in_experiment = EvaluatorBucketing.variation_index_for_user(flag, vr, user)
274-
#if in experiment is true, set reason to a different reason instance/singleton with in_experiment set
275-
if in_experiment && reason.kind == :FALLTHROUGH
276-
reason = EvaluationReason::fallthrough(in_experiment)
277-
elsif in_experiment && reason.kind == :RULE_MATCH
278-
reason = EvaluationReason::rule_match(reason.rule_index, reason.rule_id, in_experiment)
279-
end
280259
if index.nil?
281260
@logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": variation/rollout object with no variation or rollout")
282261
return Evaluator.error_result(EvaluationReason::ERROR_MALFORMED_FLAG)
283262
end
284-
return get_variation(flag, index, reason)
263+
if precomputed_results
264+
return precomputed_results.for_variation(index, in_experiment)
265+
else
266+
#if in experiment is true, set reason to a different reason instance/singleton with in_experiment set
267+
if in_experiment
268+
if reason.kind == :FALLTHROUGH
269+
reason = EvaluationReason::fallthrough(in_experiment)
270+
elsif reason.kind == :RULE_MATCH
271+
reason = EvaluationReason::rule_match(reason.rule_index, reason.rule_id, in_experiment)
272+
end
273+
end
274+
return EvaluatorHelpers.evaluation_detail_for_variation(flag, index, reason)
275+
end
285276
end
286277
end
287278
end
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require "ldclient-rb/evaluation_detail"
2+
3+
# This file contains any pieces of low-level evaluation logic that don't need to be inside the Evaluator
4+
# class, because they don't depend on any SDK state outside of their input parameters.
5+
6+
module LaunchDarkly
7+
module Impl
8+
module EvaluatorHelpers
9+
def self.off_result(flag, logger = nil)
10+
pre = flag[:_preprocessed]
11+
pre ? pre.off_result : evaluation_detail_for_off_variation(flag, EvaluationReason::off, logger)
12+
end
13+
14+
def self.target_match_result(target, flag, logger = nil)
15+
pre = target[:_preprocessed]
16+
pre ? pre.match_result : evaluation_detail_for_variation(
17+
flag, target[:variation], EvaluationReason::target_match, logger)
18+
end
19+
20+
def self.prerequisite_failed_result(prereq, flag, logger = nil)
21+
pre = prereq[:_preprocessed]
22+
pre ? pre.failed_result : evaluation_detail_for_off_variation(
23+
flag, EvaluationReason::prerequisite_failed(prereq[:key]), logger
24+
)
25+
end
26+
27+
def self.fallthrough_precomputed_results(flag)
28+
pre = flag[:_preprocessed]
29+
pre ? pre.fallthrough_factory : nil
30+
end
31+
32+
def self.rule_precomputed_results(rule)
33+
pre = rule[:_preprocessed]
34+
pre ? pre.all_match_results : nil
35+
end
36+
37+
def self.evaluation_detail_for_off_variation(flag, reason, logger = nil)
38+
index = flag[:offVariation]
39+
index.nil? ? EvaluationDetail.new(nil, nil, reason) : evaluation_detail_for_variation(flag, index, reason, logger)
40+
end
41+
42+
def self.evaluation_detail_for_variation(flag, index, reason, logger = nil)
43+
vars = flag[:variations] || []
44+
if index < 0 || index >= vars.length
45+
logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index") unless logger.nil?
46+
EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
47+
else
48+
EvaluationDetail.new(vars[index], index, reason)
49+
end
50+
end
51+
end
52+
end
53+
end

0 commit comments

Comments
 (0)