-
Notifications
You must be signed in to change notification settings - Fork 54
chore: Support flag change listeners in contract tests #368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1a7d2d2
chore: add flag change listener support to contract tests
devin-ai-integration[bot] c5b1f21
Update contract-tests/flag_change_listener.rb
keelerm84 051ffc0
chore: use private def style for store_listener method
devin-ai-integration[bot] b691036
Merge branch 'mk/sdk-1967/flag-change-listener-support' of https://gi…
devin-ai-integration[bot] b1e9380
Merge branch 'main' into mk/sdk-1967/flag-change-listener-support
keelerm84 8db9224
bump to alpha.4
keelerm84 1b84760
code review feedback
keelerm84 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| require 'http' | ||
| require 'json' | ||
|
|
||
| # | ||
| # A listener that receives FlagChange events and POSTs notifications to a callback URI. | ||
| # Implements the #update method expected by the SDK's FlagTracker. | ||
| # | ||
| class FlagChangeCallbackListener | ||
| def initialize(listener_id, callback_uri) | ||
| @listener_id = listener_id | ||
| @callback_uri = callback_uri | ||
| end | ||
|
|
||
| # @param flag_change [LaunchDarkly::Interfaces::FlagChange] | ||
| def update(flag_change) | ||
| payload = { | ||
| listenerId: @listener_id, | ||
| flagKey: flag_change.key, | ||
| } | ||
| HTTP.post(@callback_uri, json: payload) | ||
| rescue => e | ||
| # Log but don't re-raise; listener errors shouldn't crash the test service | ||
| $log.error("FlagChangeCallbackListener POST failed: #{e}") | ||
| end | ||
| end | ||
|
|
||
| # | ||
| # A listener that receives FlagValueChange events and POSTs notifications to a callback URI. | ||
| # Implements the #update method expected by the SDK's FlagTracker (via FlagValueChangeAdapter). | ||
| # | ||
| class FlagValueChangeCallbackListener | ||
| def initialize(listener_id, callback_uri) | ||
| @listener_id = listener_id | ||
| @callback_uri = callback_uri | ||
| end | ||
|
|
||
| # @param flag_value_change [LaunchDarkly::Interfaces::FlagValueChange] | ||
| def update(flag_value_change) | ||
| payload = { | ||
| listenerId: @listener_id, | ||
| flagKey: flag_value_change.key, | ||
| oldValue: flag_value_change.old_value, | ||
| newValue: flag_value_change.new_value, | ||
| } | ||
| HTTP.post(@callback_uri, json: payload) | ||
| rescue => e | ||
| $log.error("FlagValueChangeCallbackListener POST failed: #{e}") | ||
| end | ||
| end | ||
|
|
||
| # | ||
| # Manages all active flag change listener registrations for a single SDK client entity. | ||
| # Thread-safe via a Mutex. | ||
| # | ||
| class ListenerRegistry | ||
| # @param tracker [LaunchDarkly::Interfaces::FlagTracker] | ||
| def initialize(tracker) | ||
| @tracker = tracker | ||
| @mu = Mutex.new | ||
| @listeners = {} # listenerId => listener object to pass to remove_listener | ||
| end | ||
|
|
||
| # Registers a general flag change listener that fires on any flag configuration change. | ||
| # | ||
| # @param listener_id [String] | ||
| # @param callback_uri [String] | ||
| def register_flag_change_listener(listener_id, callback_uri) | ||
| listener = FlagChangeCallbackListener.new(listener_id, callback_uri) | ||
| @tracker.add_listener(listener) | ||
| store_listener(listener_id, listener) | ||
| end | ||
|
|
||
| # Registers a flag value change listener that fires when the evaluated value of a | ||
| # specific flag changes for a given context. | ||
| # | ||
| # @param listener_id [String] | ||
| # @param flag_key [String] | ||
| # @param context [LaunchDarkly::LDContext] | ||
| # @param callback_uri [String] | ||
| def register_flag_value_change_listener(listener_id, flag_key, context, callback_uri) | ||
| inner_listener = FlagValueChangeCallbackListener.new(listener_id, callback_uri) | ||
| # add_flag_value_change_listener returns the adapter object that must be passed to | ||
| # remove_listener for unregistration. | ||
| adapter = @tracker.add_flag_value_change_listener(flag_key, context, inner_listener) | ||
| store_listener(listener_id, adapter) | ||
| end | ||
|
|
||
| # Unregisters a previously registered listener by its ID. | ||
| # | ||
| # @param listener_id [String] | ||
| # @return [Boolean] true if the listener was found and removed | ||
| def unregister(listener_id) | ||
| listener = nil | ||
| @mu.synchronize do | ||
| listener = @listeners.delete(listener_id) | ||
| end | ||
|
|
||
| return false if listener.nil? | ||
|
|
||
| @tracker.remove_listener(listener) | ||
| true | ||
| end | ||
|
|
||
| # Removes all registered listeners. Called when the SDK client entity shuts down. | ||
| def close_all | ||
| listeners_to_remove = nil | ||
| @mu.synchronize do | ||
| listeners_to_remove = @listeners.values | ||
| @listeners = {} | ||
| end | ||
|
|
||
| listeners_to_remove.each do |listener| | ||
| @tracker.remove_listener(listener) | ||
| end | ||
| end | ||
|
|
||
| # Stores a listener, cancelling any previously registered listener with the same ID. | ||
| private def store_listener(listener_id, listener) | ||
| old_listener = nil | ||
| @mu.synchronize do | ||
| old_listener = @listeners[listener_id] | ||
| @listeners[listener_id] = listener | ||
| end | ||
|
|
||
| @tracker.remove_listener(old_listener) if old_listener | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.