diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ebbe669..1a3e75e 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,7 +22,7 @@ jobs: - name: Analysing the code with pylint run: | pylint featuremanagement - - uses: psf/black@stable + - uses: psf/black@24.8.0 - name: Run mypy run: | mypy featuremanagement diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae64d8..2cca6db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Release History +## 2.0.0b2 (Unreleased) + +* Adds VariantAllocationPercentage, DefaultWhenEnabled, and AllocationId to telemetry. +* Allocation seed value is now None by default, and only defaults to `allocation\n` when assigning variants. + ## 2.0.0b1 (09/10/2024) * Adds support for Feature Variants. diff --git a/docs/conf.py b/docs/conf.py index 67f104f..0fd9889 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,7 @@ project = "FeatureManagement" copyright = "2024, Microsoft" author = "Microsoft" -release = "2.0.0b1" +release = "2.0.0b2" # -- General configuration --------------------------------------------------- diff --git a/featuremanagement/_featuremanagerbase.py b/featuremanagement/_featuremanagerbase.py index 8cad6f1..64732e5 100644 --- a/featuremanagement/_featuremanagerbase.py +++ b/featuremanagement/_featuremanagerbase.py @@ -175,7 +175,10 @@ def _assign_variant( evaluation_event.reason = VariantAssignmentReason.GROUP variant_name = group_allocation.variant if not variant_name and feature.allocation.percentile: - context_id = targeting_context.user_id + "\n" + feature.allocation.seed + seed = feature.allocation.seed + if not seed: + seed = "allocation\n" + feature.name + context_id = targeting_context.user_id + "\n" + seed box: float = self._is_targeted(context_id) for percentile_allocation in feature.allocation.percentile: if box == 100 and percentile_allocation.percentile_to == 100: diff --git a/featuremanagement/_models/_allocation.py b/featuremanagement/_models/_allocation.py index 9fd2250..2ce0fb6 100644 --- a/featuremanagement/_models/_allocation.py +++ b/featuremanagement/_models/_allocation.py @@ -103,16 +103,16 @@ class Allocation: Represents an allocation configuration for a feature flag. """ - def __init__(self, feature_name: str) -> None: + def __init__(self) -> None: self._default_when_enabled = None self._default_when_disabled = None self._user: List[UserAllocation] = [] self._group: List[GroupAllocation] = [] self._percentile: List[PercentileAllocation] = [] - self._seed = "allocation\n" + feature_name + self._seed = None @classmethod - def convert_from_json(cls, json: Dict[str, Any], feature_name: str) -> Optional["Allocation"]: + def convert_from_json(cls, json: Dict[str, Any]) -> Optional["Allocation"]: """ Convert a JSON object to Allocation. @@ -123,7 +123,7 @@ def convert_from_json(cls, json: Dict[str, Any], feature_name: str) -> Optional[ """ if not json: return None - allocation = cls(feature_name) + allocation = cls() allocation._default_when_enabled = json.get(DEFAULT_WHEN_ENABLED) allocation._default_when_disabled = json.get(DEFAULT_WHEN_DISABLED) allocation._user = [] @@ -197,7 +197,7 @@ def percentile(self) -> List[PercentileAllocation]: return self._percentile @property - def seed(self) -> str: + def seed(self) -> Optional[str]: """ Get the seed for the allocation. diff --git a/featuremanagement/_models/_feature_flag.py b/featuremanagement/_models/_feature_flag.py index 387e6f8..e0665c1 100644 --- a/featuremanagement/_models/_feature_flag.py +++ b/featuremanagement/_models/_feature_flag.py @@ -51,9 +51,7 @@ def convert_from_json(cls, json_value: Mapping[str, Any]) -> "FeatureFlag": ) else: feature_flag._conditions = FeatureConditions() - feature_flag._allocation = Allocation.convert_from_json( - json_value.get(FEATURE_FLAG_ALLOCATION, None), feature_flag._id - ) + feature_flag._allocation = Allocation.convert_from_json(json_value.get(FEATURE_FLAG_ALLOCATION, None)) if FEATURE_FLAG_VARIANTS in json_value: variants: List[Mapping[str, Any]] = json_value.get(FEATURE_FLAG_VARIANTS, []) feature_flag._variants = [] @@ -66,7 +64,7 @@ def convert_from_json(cls, json_value: Mapping[str, Any]) -> "FeatureFlag": return feature_flag @property - def name(self) -> Optional[str]: + def name(self) -> str: """ Get the name of the feature flag. diff --git a/featuremanagement/_version.py b/featuremanagement/_version.py index 32e04dd..99e8b1d 100644 --- a/featuremanagement/_version.py +++ b/featuremanagement/_version.py @@ -4,4 +4,4 @@ # license information. # ------------------------------------------------------------------------- -VERSION = "2.0.0b1" +VERSION = "2.0.0b2" diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 0a8f946..bd84fd7 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -25,6 +25,8 @@ EVENT_NAME = "FeatureEvaluation" +EVALUATION_EVENT_VERSION = "1.0.0" + def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, Optional[str]]] = None) -> None: """ @@ -51,17 +53,49 @@ def publish_telemetry(evaluation_event: EvaluationEvent) -> None: """ if not HAS_AZURE_MONITOR_EVENTS_EXTENSION: return - event = {} - if evaluation_event.feature: - event[FEATURE_NAME] = evaluation_event.feature.name + event: Dict[str, Optional[str]] = {} + if not evaluation_event.feature: + return + event[FEATURE_NAME] = evaluation_event.feature.name event[ENABLED] = str(evaluation_event.enabled) + event["Version"] = EVALUATION_EVENT_VERSION + # VariantAllocationPercentage if evaluation_event.reason and evaluation_event.reason != VariantAssignmentReason.NONE: if evaluation_event.variant: event[VARIANT] = evaluation_event.variant.name event[REASON] = evaluation_event.reason.value - if evaluation_event.feature and evaluation_event.feature.telemetry: + if evaluation_event.reason == VariantAssignmentReason.DEFAULT_WHEN_ENABLED: + allocation_percentage = 0 + + if evaluation_event.feature.allocation and evaluation_event.feature.allocation.percentile: + for allocation in evaluation_event.feature.allocation.percentile: + if ( + evaluation_event.variant + and allocation.variant == evaluation_event.variant.name + and allocation.percentile_to + ): + allocation_percentage += allocation.percentile_to - allocation.percentile_from + + event["VariantAssignmentPercentage"] = str(100 - allocation_percentage) + elif evaluation_event.reason == VariantAssignmentReason.PERCENTILE: + if evaluation_event.feature.allocation and evaluation_event.feature.allocation.percentile: + allocation_percentage = 0 + for allocation in evaluation_event.feature.allocation.percentile: + if ( + evaluation_event.variant + and allocation.variant == evaluation_event.variant.name + and allocation.percentile_to + ): + allocation_percentage += allocation.percentile_to - allocation.percentile_from + event["VariantAssignmentPercentage"] = str(allocation_percentage) + + # DefaultWhenEnabled + if evaluation_event.feature.allocation and evaluation_event.feature.allocation.default_when_enabled: + event["DefaultWhenEnabled"] = evaluation_event.feature.allocation.default_when_enabled + + if evaluation_event.feature.telemetry: for metadata_key, metadata_value in evaluation_event.feature.telemetry.metadata.items(): if metadata_key not in event: event[metadata_key] = metadata_value diff --git a/pyproject.toml b/pyproject.toml index e2aed8b..8da891e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta" [project] name = "FeatureManagement" -version = "2.0.0b1" +version = "2.0.0b2" authors = [ { name="Microsoft Corporation", email="appconfig@microsoft.com" }, ]