diff --git a/src/backend/core/analytics/__init__.py b/src/backend/core/analytics/__init__.py index d5d4148c..8dbabe4f 100644 --- a/src/backend/core/analytics/__init__.py +++ b/src/backend/core/analytics/__init__.py @@ -19,6 +19,7 @@ from django.utils.module_loading import import_string from .base import AnalyticsBackend, NoOpAnalytics from .events import AnalyticsEvent +from .user_feature_flags import UserFeatureFlag __all__ = [ "get_analytics", @@ -26,6 +27,8 @@ __all__ = [ "capture", "AnalyticsBackend", "AnalyticsEvent", + "is_user_feature_flag_enabled", + "UserFeatureFlag", ] @@ -57,3 +60,8 @@ def capture( ) -> None: """Record an event performed by an identified user.""" 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) diff --git a/src/backend/core/analytics/base.py b/src/backend/core/analytics/base.py index 0f6ab388..9bcd4175 100644 --- a/src/backend/core/analytics/base.py +++ b/src/backend/core/analytics/base.py @@ -1,10 +1,11 @@ """Analytics backend protocol and default no-op implementation.""" from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Mapping from ..models import User from .events import AnalyticsEvent +from .user_feature_flags import UserFeatureFlag class AnalyticsBackend(ABC): @@ -35,6 +36,21 @@ class AnalyticsBackend(ABC): def shutdown(self) -> None: """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): """Default backend: silently discards everything.""" diff --git a/src/backend/core/analytics/user_feature_flags.py b/src/backend/core/analytics/user_feature_flags.py new file mode 100644 index 00000000..145cc323 --- /dev/null +++ b/src/backend/core/analytics/user_feature_flags.py @@ -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"