Skip to content
Open
5 changes: 3 additions & 2 deletions api/custom_auth/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ class Meta(UserCreateSerializer.Meta): # type: ignore[misc]
"is_active",
"marketing_consent_given",
"uuid",
"sign_up_type",
)
read_only_fields = ("is_active", "uuid", "marketing_consent_given")
write_only_fields = ("sign_up_type",)
extra_kwargs = {
"sign_up_type": {"required": False},
"email": {
"validators": list[object](),
}
},
}

def validate(self, attrs): # type: ignore[no-untyped-def]
Expand Down
27 changes: 25 additions & 2 deletions api/organisations/task_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ def send_api_flags_blocked_notification(organisation: Organisation) -> None:
userorganisation__organisation=organisation,
)

recipient_emails = list(recipient_list.values_list("email", flat=True))
if not recipient_emails:
logger.warning(
"notification.no_recipients_for_blocked_notification",
organisation__id=organisation.id,
)
return

url = get_current_site_url()
context = {
"organisation": organisation,
Expand All @@ -43,7 +51,7 @@ def send_api_flags_blocked_notification(organisation: Organisation) -> None:
subject="Flagsmith API use has been blocked due to overuse",
message=render_to_string(message, context),
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=list(recipient_list.values_list("email", flat=True)),
recipient_list=recipient_emails,
html_message=render_to_string(html_message, context),
fail_silently=True,
)
Expand Down Expand Up @@ -75,6 +83,21 @@ def _send_api_usage_notification(
message = "organisations/api_usage_notification_limit.txt"
html_message = "organisations/api_usage_notification_limit.html"

recipient_emails = list(recipient_list.values_list("email", flat=True))

if not recipient_emails:
logger.warning(
"notification.no_recipients",
organisation__id=organisation.id,
matched_threshold=matched_threshold,
)
OrganisationAPIUsageNotification.objects.create(
organisation=organisation,
percent_usage=matched_threshold,
notified_at=timezone.now(),
)
return

url = get_current_site_url()
context = {
"organisation": organisation,
Expand All @@ -87,7 +110,7 @@ def _send_api_usage_notification(
subject=f"Flagsmith API use has reached {matched_threshold}%",
message=render_to_string(message, context),
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=list(recipient_list.values_list("email", flat=True)),
recipient_list=recipient_emails,
html_message=render_to_string(html_message, context),
fail_silently=True,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ def test_register__with_sign_up_type__stores_sign_up_type(client, db): # type:
)

# Then
print(response.content)
assert response.status_code == status.HTTP_201_CREATED

response_json = response.json()
Expand Down
64 changes: 64 additions & 0 deletions api/tests/unit/organisations/test_unit_organisations_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from organisations.subscriptions.xero.metadata import XeroSubscriptionMetadata
from organisations.task_helpers import (
handle_api_usage_notification_for_organisation,
send_api_flags_blocked_notification,
)
from organisations.tasks import ( # type: ignore[attr-defined]
ALERT_EMAIL_MESSAGE,
Expand Down Expand Up @@ -530,6 +531,45 @@ def test_handle_api_usage_notifications__usage_below_100_percent__sends_90_perce
)


@pytest.mark.django_db
@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00")
Comment thread
SahilJat marked this conversation as resolved.
def test_handle_api_usage_notifications__no_admin_users__skips_notification(
mocker: MockerFixture,
mailoutbox: list[EmailMultiAlternatives],
log: StructuredLogCapture,
enable_features: EnableFeaturesFixture,
) -> None:
# Given - an organisation with no users
organisation = Organisation.objects.create(name="No Users Org")
now = timezone.now()
organisation.subscription.plan = SCALE_UP
organisation.subscription.subscription_id = "fancy_id"
organisation.subscription.save()
OrganisationSubscriptionInformationCache.objects.create(
organisation=organisation,
allowed_30d_api_calls=100,
current_billing_term_starts_at=now - timedelta(days=45),
current_billing_term_ends_at=now + timedelta(days=320),
api_calls_30d=91,
)
mock_api_usage = mocker.patch(
"organisations.task_helpers.get_current_api_usage",
)
mock_api_usage.return_value = 91
enable_features("api_usage_alerting")

# When
handle_api_usage_notifications()

# Then - no email sent, warning logged
assert len(mailoutbox) == 0
assert any(e.get("event") == "notification.no_recipients" for e in log.events)
assert OrganisationAPIUsageNotification.objects.filter(
organisation=organisation,
percent_usage=90,
).exists()


@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00")
def test_handle_api_usage_notifications__usage_below_alert_thresholds__sends_no_email(
mocker: MockerFixture,
Expand Down Expand Up @@ -2136,3 +2176,27 @@ def test_update_organisation_subscription_information_cache__called__calls_updat
SubscriptionCacheEntity.CHARGEBEE, SubscriptionCacheEntity.API_USAGE
)
]


@pytest.mark.django_db
def test_send_api_flags_blocked_notification__no_recipients__skips_notification(
organisation: Organisation,
log: StructuredLogCapture,
mailoutbox: list[EmailMultiAlternatives],
) -> None:
# Given
# Ensure no users are associated with the organisation
UserOrganisation.objects.filter(organisation=organisation).delete()

# When
send_api_flags_blocked_notification(organisation)

# Then
assert len(mailoutbox) == 0
assert log.events == [
{
"level": "warning",
"event": "notification.no_recipients_for_blocked_notification",
"organisation__id": organisation.id,
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
### `api_usage.notification.evaluated`

Logged at `info` from:
- `api/organisations/task_helpers.py:153`
- `api/organisations/task_helpers.py:176`

Attributes:
- `allowed_api_calls`
Expand All @@ -16,15 +16,32 @@ Attributes:
### `api_usage.notification.missing_billing_starts_at`

Logged at `error` from:
- `api/organisations/task_helpers.py:118`
- `api/organisations/task_helpers.py:141`

Attributes:
- `organisation.id`

### `api_usage.notification.no_recipients`

Logged at `warning` from:
- `api/organisations/task_helpers.py:89`

Attributes:
- `matched_threshold`
- `organisation.id`

### `api_usage.notification.no_recipients_for_blocked_notification`

Logged at `warning` from:
- `api/organisations/task_helpers.py:34`

Attributes:
- `organisation.id`

### `api_usage.notification.sent`

Logged at `info` from:
- `api/organisations/task_helpers.py:176`
- `api/organisations/task_helpers.py:199`

Attributes:
- `matched_threshold`
Expand Down
Loading