Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 43
feat: Implement tracking#564
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
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
1cbc3d5
add: Implement tracking
danjuv 55e8a2f
chore: update README.md
danjuv 4525b01
add: track method to providers
danjuv e04c58b
add: add TrackingEventDetails definition
danjuv 2501cbb
add: add tests
danjuv 8a5cf75
fix: add newlines
danjuv 9e2ef06
chore: update test decorators
danjuv b1170e1
chore: add docstring for track method
danjuv d35769e
refactor: rename to eval_context_attributes
danjuv cce9a7a
refactor: better naming for tracking events for inmemory provider
danjuv 8ca3e73
chore: add Tracking to list of features in README
danjuv 48ecabb
chore: fix example syntax in README
danjuv 7a0993e
feat: add track method to base FeatureProvider class
danjuv 63c5a5b
chore: remove unnecessary async from tests
danjuv 6451cec
chore: fix typing
danjuv 1bb99e1
chore: fix typo in readme
danjuv 4adaa5b
fix: use hasattr to check for track method
danjuv 32d972e
chore: fix example in README
danjuv 7780fa4
chore: run pre-commit
danjuv 7a24de9
remove unnecessary track definitions
danjuv 1bf33c7
fix: remove unused import
danjuv cdadfaf
fix: remove unnecessary async
danjuv bb65dbd
chore: use correct import path in README
danjuv ba7fa40
Merge remote-tracking branch 'origin/main' into feat/add-tracking
danjuv bfbbf4c
fix: deduplicate tracking_event_details checks
danjuv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -107,6 +107,7 @@ print("Value: " + str(flag_value)) | ||
| | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ||
| | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ||
| | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ||
| | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ||
| | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ||
| | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) | | ||
| | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | | ||
| @@ -185,6 +186,27 @@ client.add_hooks([MyHook()]) | ||
| options = FlagEvaluationOptions(hooks=[MyHook()]) | ||
| client.get_boolean_flag("my-flag", False, flag_evaluation_options=options) | ||
| ``` | ||
| ### Tracking | ||
| The [tracking API](https://openfeature.dev/specification/sections/tracking/) allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. | ||
| This is essential for robust experimentation powered by feature flags. | ||
| For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. | ||
| ```python | ||
| from openfeature.track import TrackingEventDetails | ||
| # initialize a client | ||
| client = api.get_client() | ||
| # trigger tracking event action | ||
| client.track( | ||
| 'visited-promo-page', | ||
| evaluation_context=EvaluationContext(), | ||
| tracking_event_details=TrackingEventDetails(99.77).add("currencyCode", "USD"), | ||
| ) | ||
| ``` | ||
danjuv marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Note that some providers may not support tracking; check the documentation for your provider for more information. | ||
| ### Logging | ||
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 |
|---|---|---|
| @@ -32,6 +32,7 @@ | ||
| ) | ||
| from openfeature.provider import FeatureProvider, ProviderStatus | ||
| from openfeature.provider._registry import provider_registry | ||
| from openfeature.track import TrackingEventDetails | ||
| from openfeature.transaction_context import get_transaction_context | ||
| __all__ = [ | ||
| @@ -955,6 +956,33 @@ def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None: | ||
| def remove_handler(self, event: ProviderEvent, handler: EventHandler) -> None: | ||
| _event_support.remove_client_handler(self, event, handler) | ||
| def track( | ||
| self, | ||
| tracking_event_name: str, | ||
| evaluation_context: EvaluationContext | None = None, | ||
| tracking_event_details: TrackingEventDetails | None = None, | ||
| ) -> None: | ||
| """ | ||
| Tracks the occurrence of a particular action or application state. | ||
| :param tracking_event_name: the name of the tracking event | ||
| :param evaluation_context: the evaluation context | ||
| :param tracking_event_details: Optional data relevant to the tracking event | ||
| """ | ||
| if evaluation_context is None: | ||
| evaluation_context = EvaluationContext() | ||
| merged_eval_context = ( | ||
| get_evaluation_context() | ||
| .merge(get_transaction_context()) | ||
| .merge(self.context) | ||
| .merge(evaluation_context) | ||
danjuv marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ) | ||
| self.provider.track( | ||
| tracking_event_name, merged_eval_context, tracking_event_details | ||
| ) | ||
| def _typecheck_flag_value( | ||
| value: typing.Any, flag_type: FlagType | ||
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,25 @@ | ||
| from __future__ import annotations | ||
| import typing | ||
| from collections.abc import Mapping, Sequence | ||
| TrackingValue: typing.TypeAlias = ( | ||
| bool | int | float | str | Sequence["TrackingValue"] | Mapping[str, "TrackingValue"] | ||
| ) | ||
| class TrackingEventDetails: | ||
| value: float | None | ||
| attributes: dict[str, TrackingValue] | ||
| def __init__( | ||
| self, | ||
| value: float | None = None, | ||
| attributes: dict[str, TrackingValue] | None = None, | ||
| ): | ||
| self.value = value | ||
| self.attributes = attributes or {} | ||
| def add(self, key: str, value: TrackingValue) -> TrackingEventDetails: | ||
| self.attributes[key] = value | ||
| return self |
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,39 @@ | ||
| from openfeature.track import TrackingEventDetails | ||
| def test_add_attribute_to_tracking_event_details(): | ||
| tracking_event_details = TrackingEventDetails() | ||
| tracking_event_details.add("key", "value") | ||
| assert tracking_event_details.attributes == {"key": "value"} | ||
| def test_add_attribute_to_tracking_event_details_dict(): | ||
| tracking_event_details = TrackingEventDetails() | ||
| tracking_event_details.add("key", {"key1": "value1", "key2": "value2"}) | ||
| assert tracking_event_details.attributes == { | ||
| "key": {"key1": "value1", "key2": "value2"} | ||
| } | ||
| def test_get_value_from_tracking_event_details(): | ||
| tracking_event_details = TrackingEventDetails(value=1) | ||
| assert tracking_event_details.value == 1 | ||
| def test_get_attributes_from_tracking_event_details(): | ||
| tracking_event_details = TrackingEventDetails( | ||
| value=5.0, attributes={"key": "value"} | ||
| ) | ||
| assert tracking_event_details.attributes == {"key": "value"} | ||
| def test_get_attributes_from_tracking_event_details_with_none_value(): | ||
| tracking_event_details = TrackingEventDetails(attributes={"key": "value"}) | ||
| assert tracking_event_details.attributes == {"key": "value"} | ||
| assert tracking_event_details.value is None | ||
| def test_get_attributes_from_tracking_event_details_with_none_attributes(): | ||
| tracking_event_details = TrackingEventDetails(value=5.0) | ||
| assert tracking_event_details.attributes == {} | ||
| assert tracking_event_details.value == 5.0 |
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
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.