zilencer: Return can_push info at the push/notify endpoint.

This provides the remote server this information to refresh it on its
Realm attributes whenever it sends a push notification.

Fixes #27483.
This commit is contained in:
Mateusz Mandera 2023-12-11 22:21:48 +01:00 committed by Tim Abbott
parent b09f3a2da1
commit c1988a14a7
4 changed files with 70 additions and 26 deletions

View File

@ -46,6 +46,7 @@ from zerver.lib.send_email import (
send_email_to_billing_admins_and_realm_owners, send_email_to_billing_admins_and_realm_owners,
) )
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
from zerver.lib.types import RemoteRealmDictValue
from zerver.lib.url_encoding import append_url_query_string from zerver.lib.url_encoding import append_url_query_string
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
from zerver.models import ( from zerver.models import (
@ -3244,6 +3245,28 @@ class RemoteRealmBillingSession(BillingSession): # nocoverage
if plan is None: if plan is None:
return return
def get_push_service_validity_dict(self) -> RemoteRealmDictValue:
customer = self.get_customer()
if customer is None:
return {"can_push": True, "expected_end_timestamp": None}
current_plan = get_current_plan_by_customer(customer)
if current_plan is None:
return {"can_push": True, "expected_end_timestamp": None}
expected_end_timestamp = None
if current_plan.status in [
CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL,
]:
expected_end_timestamp = datetime_to_timestamp(
self.get_next_billing_cycle(current_plan)
)
return {
"can_push": True,
"expected_end_timestamp": expected_end_timestamp,
}
class RemoteServerBillingSession(BillingSession): # nocoverage class RemoteServerBillingSession(BillingSession): # nocoverage
"""Billing session for pre-8.0 servers that do not yet support """Billing session for pre-8.0 servers that do not yet support

View File

@ -641,6 +641,21 @@ def send_notifications_to_bouncer(
increment=total_android_devices + total_apple_devices, increment=total_android_devices + total_apple_devices,
) )
remote_realm_dict = response_data.get("realm")
if remote_realm_dict is not None:
# The server may have updated our understanding of whether
# push notifications will work.
assert isinstance(remote_realm_dict, dict)
do_set_realm_property(
user_profile.realm,
"push_notifications_enabled",
remote_realm_dict["can_push"],
acting_user=None,
)
do_set_push_notifications_enabled_end_timestamp(
user_profile.realm, remote_realm_dict["expected_end_timestamp"], acting_user=None
)
return total_android_devices, total_apple_devices return total_android_devices, total_apple_devices

View File

@ -591,6 +591,7 @@ class PushBouncerNotificationTest(BouncerTestCase):
payload = { payload = {
"user_id": hamlet.id, "user_id": hamlet.id,
"user_uuid": str(hamlet.uuid), "user_uuid": str(hamlet.uuid),
"realm_uuid": str(hamlet.realm.uuid),
"gcm_payload": {"event": "remove", "zulip_message_ids": many_ids}, "gcm_payload": {"event": "remove", "zulip_message_ids": many_ids},
"apns_payload": { "apns_payload": {
"badge": 0, "badge": 0,
@ -619,6 +620,7 @@ class PushBouncerNotificationTest(BouncerTestCase):
"total_android_devices": 2, "total_android_devices": 2,
"total_apple_devices": 1, "total_apple_devices": 1,
"deleted_devices": {"android_devices": [], "apple_devices": []}, "deleted_devices": {"android_devices": [], "apple_devices": []},
"realm": {"can_push": True, "expected_end_timestamp": None},
}, },
data, data,
) )
@ -1813,7 +1815,9 @@ class AnalyticsBouncerTest(BouncerTestCase):
with mock.patch( with mock.patch(
"zilencer.views.RemoteRealmBillingSession.get_customer", return_value=dummy_customer "zilencer.views.RemoteRealmBillingSession.get_customer", return_value=dummy_customer
): ):
with mock.patch("zilencer.views.get_current_plan_by_customer", return_value=None) as m: with mock.patch(
"corporate.lib.stripe.get_current_plan_by_customer", return_value=None
) as m:
send_server_data_to_push_bouncer(consider_usage_statistics=False) send_server_data_to_push_bouncer(consider_usage_statistics=False)
m.assert_called() m.assert_called()
realms = Realm.objects.all() realms = Realm.objects.all()
@ -1828,7 +1832,8 @@ class AnalyticsBouncerTest(BouncerTestCase):
"zilencer.views.RemoteRealmBillingSession.get_customer", return_value=dummy_customer "zilencer.views.RemoteRealmBillingSession.get_customer", return_value=dummy_customer
): ):
with mock.patch( with mock.patch(
"zilencer.views.get_current_plan_by_customer", return_value=dummy_customer_plan "corporate.lib.stripe.get_current_plan_by_customer",
return_value=dummy_customer_plan,
): ):
with mock.patch( with mock.patch(
"zilencer.views.RemoteRealmBillingSession.get_next_billing_cycle", "zilencer.views.RemoteRealmBillingSession.get_next_billing_cycle",
@ -3614,6 +3619,7 @@ class TestSendNotificationsToBouncer(PushNotificationTest):
android_devices=[device.token for device in android_devices], android_devices=[device.token for device in android_devices],
apple_devices=[device.token for device in apple_devices], apple_devices=[device.token for device in apple_devices],
), ),
"realm": {"can_push": True, "expected_end_timestamp": None},
} }
total_android_devices, total_apple_devices = send_notifications_to_bouncer( total_android_devices, total_apple_devices = send_notifications_to_bouncer(
user, {"apns": True}, {"gcm": True}, {}, list(android_devices), list(apple_devices) user, {"apns": True}, {"gcm": True}, {}, list(android_devices), list(apple_devices)
@ -3650,6 +3656,23 @@ class TestSendNotificationsToBouncer(PushNotificationTest):
self.assertEqual(PushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0) self.assertEqual(PushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0)
self.assertEqual(PushDeviceToken.objects.filter(kind=PushDeviceToken.GCM).count(), 0) self.assertEqual(PushDeviceToken.objects.filter(kind=PushDeviceToken.GCM).count(), 0)
# Now simulating getting "can_push" as False from the bouncer and verify
# that we update the realm value.
mock_send.return_value = {
"total_android_devices": 1,
"total_apple_devices": 3,
"realm": {"can_push": False, "expected_end_timestamp": None},
"deleted_devices": DevicesToCleanUpDict(
android_devices=[],
apple_devices=[],
),
}
total_android_devices, total_apple_devices = send_notifications_to_bouncer(
user, {"apns": True}, {"gcm": True}, {}, list(android_devices), list(apple_devices)
)
user.realm.refresh_from_db()
self.assertEqual(user.realm.push_notifications_enabled, False)
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com") @override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
class TestSendToPushBouncer(ZulipTestCase): class TestSendToPushBouncer(ZulipTestCase):

View File

@ -47,7 +47,7 @@ from zerver.lib.remote_server import (
) )
from zerver.lib.request import REQ, has_request_variables from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.timestamp import timestamp_to_datetime
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.types import RemoteRealmDictValue from zerver.lib.types import RemoteRealmDictValue
from zerver.lib.validator import check_capped_string, check_int, check_string_fixed_length from zerver.lib.validator import check_capped_string, check_int, check_string_fixed_length
@ -514,6 +514,8 @@ def remote_server_notify_push(
timezone_now(), timezone_now(),
increment=android_successfully_delivered + apple_successfully_delivered, increment=android_successfully_delivered + apple_successfully_delivered,
) )
remote_realm_dict: Optional[RemoteRealmDictValue] = None
if remote_realm is not None: if remote_realm is not None:
do_increment_logging_stat( do_increment_logging_stat(
remote_realm, remote_realm,
@ -522,6 +524,8 @@ def remote_server_notify_push(
timezone_now(), timezone_now(),
increment=android_successfully_delivered + apple_successfully_delivered, increment=android_successfully_delivered + apple_successfully_delivered,
) )
billing_session = RemoteRealmBillingSession(remote_realm)
remote_realm_dict = billing_session.get_push_service_validity_dict()
deleted_devices = get_deleted_devices( deleted_devices = get_deleted_devices(
user_identity, user_identity,
@ -536,6 +540,7 @@ def remote_server_notify_push(
"total_android_devices": len(android_devices), "total_android_devices": len(android_devices),
"total_apple_devices": len(apple_devices), "total_apple_devices": len(apple_devices),
"deleted_devices": deleted_devices, "deleted_devices": deleted_devices,
"realm": remote_realm_dict,
}, },
) )
@ -968,29 +973,7 @@ def remote_server_post_analytics(
for remote_realm in remote_realms: for remote_realm in remote_realms:
uuid = str(remote_realm.uuid) uuid = str(remote_realm.uuid)
billing_session = RemoteRealmBillingSession(remote_realm) billing_session = RemoteRealmBillingSession(remote_realm)
remote_realm_dict[uuid] = billing_session.get_push_service_validity_dict()
customer = billing_session.get_customer()
if customer is None:
remote_realm_dict[uuid] = {"can_push": True, "expected_end_timestamp": None}
continue
current_plan = get_current_plan_by_customer(customer)
if current_plan is None:
remote_realm_dict[uuid] = {"can_push": True, "expected_end_timestamp": None}
continue
expected_end_timestamp = None
if current_plan.status in [
CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL,
]:
expected_end_timestamp = datetime_to_timestamp(
billing_session.get_next_billing_cycle(current_plan)
)
remote_realm_dict[uuid] = {
"can_push": True,
"expected_end_timestamp": expected_end_timestamp,
}
return json_success(request, data={"realms": remote_realm_dict}) return json_success(request, data={"realms": remote_realm_dict})