(backend) extend analytics module to support feature flags

Extend the AnalyticsBackend class to support
user feature flags, without a breaking change to the
current implementation.

A flag value is considered to be a bool or a string.
This commit is contained in:
Florent Chehab
2026-07-06 21:38:13 +02:00
parent b00c4e5d6d
commit 8edcb6e973
3 changed files with 34 additions and 1 deletions
+8
View File
@@ -19,6 +19,7 @@ from django.utils.module_loading import import_string
from .base import AnalyticsBackend, NoOpAnalytics from .base import AnalyticsBackend, NoOpAnalytics
from .events import AnalyticsEvent from .events import AnalyticsEvent
from .user_feature_flags import UserFeatureFlag
__all__ = [ __all__ = [
"get_analytics", "get_analytics",
@@ -26,6 +27,8 @@ __all__ = [
"capture", "capture",
"AnalyticsBackend", "AnalyticsBackend",
"AnalyticsEvent", "AnalyticsEvent",
"is_user_feature_flag_enabled",
"UserFeatureFlag",
] ]
@@ -57,3 +60,8 @@ def capture(
) -> None: ) -> None:
"""Record an event performed by an identified user.""" """Record an event performed by an identified user."""
analytics_instance.capture(user, event, properties) analytics_instance.capture(user, event, properties)
def is_user_feature_flag_enabled(user, feature_name: UserFeatureFlag) -> bool:
"""Check if a feature is enabled at the user level."""
return analytics_instance.is_user_feature_enabled(user, feature_name)
+17 -1
View File
@@ -1,10 +1,11 @@
"""Analytics backend protocol and default no-op implementation.""" """Analytics backend protocol and default no-op implementation."""
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any from typing import Any, Mapping
from ..models import User from ..models import User
from .events import AnalyticsEvent from .events import AnalyticsEvent
from .user_feature_flags import UserFeatureFlag
class AnalyticsBackend(ABC): class AnalyticsBackend(ABC):
@@ -35,6 +36,21 @@ class AnalyticsBackend(ABC):
def shutdown(self) -> None: def shutdown(self) -> None:
"""Flush pending events. Called on process exit.""" """Flush pending events. Called on process exit."""
def get_user_feature_flags(
self,
user: User, # pylint: disable=unused-argument
) -> Mapping[UserFeatureFlag, bool | str | None]:
"""Return a dict of feature flags for the given user."""
# We return an empty dict here by default to avoid a breaking change
# By making this method abstract.
return {}
def is_user_feature_enabled(
self, user: User, feature_name: UserFeatureFlag
) -> bool:
"""Check if a feature is enabled at the user level."""
return self.get_user_feature_flags(user).get(feature_name, False) is True
class NoOpAnalytics(AnalyticsBackend): class NoOpAnalytics(AnalyticsBackend):
"""Default backend: silently discards everything.""" """Default backend: silently discards everything."""
@@ -0,0 +1,9 @@
"""Catalog of all analytics feature flags used by the backend."""
from enum import StrEnum
class UserFeatureFlag(StrEnum):
"""All feature flags configured in the app."""
TRANSCRIPT_SUMMARY_ENABLED = "summary-enabled"