Browse Source

feat(features): Add OPTIONS strategy for handling features (#69214)

This new strategy will allow us to auto configure
`AutoRegisterOptionBackedRolloutFeatureHandler` without a pr in
getsentry. Just a stopgap until we have flagpole.

To use this, just define your flag like
`manager.add("projects:some-test", OrganizationFeature,
FeatureHandlerStrategy.OPTIONS)`

I will also provide a tool that will spit out the option names for your
feature for use with automator
Dan Fuller 10 months ago
parent
commit
34369ccdb5

+ 1 - 0
src/sentry/features/__init__.py

@@ -66,6 +66,7 @@ requires_snuba = (
 # expose public api
 add = default_manager.add
 entity_features = default_manager.entity_features
+option_features = default_manager.option_features
 get = default_manager.get
 has = default_manager.has
 batch_has = default_manager.batch_has

+ 2 - 0
src/sentry/features/base.py

@@ -95,3 +95,5 @@ class FeatureHandlerStrategy(Enum):
     """Handle the feature using a constant or logic within python"""
     REMOTE = 2
     """Handle the feature using a remote flag management service"""
+    OPTIONS = 3
+    """Handle the feature using options"""

+ 7 - 0
src/sentry/features/manager.py

@@ -129,6 +129,7 @@ class FeatureManager(RegisteredFeatureManager):
         super().__init__()
         self._feature_registry: MutableMapping[str, type[Feature]] = {}
         self.entity_features: MutableSet[str] = set()
+        self.option_features: MutableSet[str] = set()
         self._entity_handler: FeatureHandler | None = None
 
     def all(self, feature_type: type[Feature] = Feature) -> Mapping[str, type[Feature]]:
@@ -158,6 +159,12 @@ class FeatureManager(RegisteredFeatureManager):
             if name.startswith("users:"):
                 raise NotImplementedError("User flags not allowed with entity_feature=True")
             self.entity_features.add(name)
+        if entity_feature_strategy == FeatureHandlerStrategy.OPTIONS:
+            if name.startswith("users:") or name.startswith("projects:"):
+                raise NotImplementedError(
+                    "OPTIONS feature handler strategy only supports organizations (for now)"
+                )
+            self.option_features.add(name)
         self._feature_registry[name] = cls
 
     def _get_feature_class(self, name: str) -> type[Feature]:

+ 13 - 0
tests/sentry/features/test_manager.py

@@ -1,6 +1,7 @@
 from typing import Any
 from unittest import mock
 
+import pytest
 from django.conf import settings
 
 from sentry import features
@@ -303,3 +304,15 @@ class FeatureManagerTest(TestCase):
 
         assert list(manager.all().keys()) == ["feat:org", "feat:project", "feat:system"]
         assert list(manager.all(OrganizationFeature).keys()) == ["feat:org"]
+
+    def test_option_features(self):
+        manager = features.FeatureManager()
+        manager.add("organizations:some-test", OrganizationFeature, FeatureHandlerStrategy.OPTIONS)
+        assert manager.option_features == {"organizations:some-test"}
+
+    def test_invalid_option_features(self):
+        manager = features.FeatureManager()
+        with pytest.raises(NotImplementedError):
+            manager.add("projects:some-test", OrganizationFeature, FeatureHandlerStrategy.OPTIONS)
+        with pytest.raises(NotImplementedError):
+            manager.add("users:some-test", OrganizationFeature, FeatureHandlerStrategy.OPTIONS)