diff --git a/posthog/api/alert.py b/posthog/api/alert.py index 0331b09a4e7..df4d2b9791c 100644 --- a/posthog/api/alert.py +++ b/posthog/api/alert.py @@ -18,6 +18,7 @@ from posthog.api.insight import InsightBasicSerializer from posthog.utils import relative_date_parse from zoneinfo import ZoneInfo +from posthog.constants import AvailableFeature class ThresholdSerializer(serializers.ModelSerializer): @@ -246,13 +247,39 @@ class AlertSerializer(serializers.ModelSerializer): if attrs.get("insight") and attrs["insight"].team.id != self.context["team_id"]: raise ValidationError({"insight": ["This insight does not belong to your team."]}) - if attrs.get("enabled") is not False and ( - AlertConfiguration.objects.filter(team_id=self.context["team_id"], enabled=True).count() - >= AlertConfiguration.ALERTS_PER_TEAM - ): - raise ValidationError( - {"alert": [f"Your team has reached the limit of {AlertConfiguration.ALERTS_PER_TEAM} enabled alerts."]} - ) + # only validate alert count when creating a new alert + if self.context["request"].method != "POST": + return attrs + + user_org = self.context["request"].user.organization + + has_alerts_feature = user_org.is_feature_available(AvailableFeature.ALERTS) + + allowed_alerts_count = next( + ( + feature.get("limit") + for feature in user_org.available_product_features or [] + if feature.get("key") == AvailableFeature.ALERTS + ), + None, + ) + + existing_alerts_count = AlertConfiguration.objects.filter(team_id=self.context["team_id"]).count() + + if has_alerts_feature: + # If allowed_alerts_count is None then the user is allowed unlimited alerts + if allowed_alerts_count is not None: + # Check current count against allowed limit + if existing_alerts_count >= allowed_alerts_count: + raise ValidationError( + {"alert": [f"Your team has reached the limit of {allowed_alerts_count} alerts on your plan."]} + ) + else: + # If the org doesn't have alerts feature, limit to that on free tier + if existing_alerts_count >= AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER: + raise ValidationError( + {"alert": [f"Your plan is limited to {AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER} alerts"]} + ) return attrs diff --git a/posthog/api/test/test_alert.py b/posthog/api/test/test_alert.py index 4c56520f150..707114e420c 100644 --- a/posthog/api/test/test_alert.py +++ b/posthog/api/test/test_alert.py @@ -130,7 +130,7 @@ class TestAlert(APIBaseTest, QueryMatchingTest): assert len(list_for_another_insight.json()["results"]) == 0 def test_alert_limit(self) -> None: - with mock.patch("posthog.api.alert.AlertConfiguration.ALERTS_PER_TEAM") as alert_limit: + with mock.patch("posthog.api.alert.AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER") as alert_limit: alert_limit.__get__ = mock.Mock(return_value=1) creation_request = { diff --git a/posthog/models/alert.py b/posthog/models/alert.py index d00425327fd..48e3bc49d3d 100644 --- a/posthog/models/alert.py +++ b/posthog/models/alert.py @@ -68,7 +68,7 @@ class Threshold(CreatedMetaFields, UUIDModel): class AlertConfiguration(CreatedMetaFields, UUIDModel): - ALERTS_PER_TEAM = 5 + ALERTS_ALLOWED_ON_FREE_TIER = 2 team = models.ForeignKey("Team", on_delete=models.CASCADE) insight = models.ForeignKey("posthog.Insight", on_delete=models.CASCADE)