mirror of https://github.com/zulip/zulip.git
models: Add push_notifications_enabled & corresponding end_timestamp.
Add two fields to Realm model: *push_notifications_enabled *push_notifications_enabled_end_timestamp Co-authored-by: Prakhar Pratyush <prakhar@zulip.com>
This commit is contained in:
parent
6aa911a9b2
commit
f6c7eaf1e5
|
@ -20,6 +20,19 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## Changes in Zulip 8.0
|
||||||
|
|
||||||
|
**Feature level 231**
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue):
|
||||||
|
`realm_push_notifications_enabled` now represents more accurately
|
||||||
|
whether push notifications are actually enabled via the mobile push
|
||||||
|
notifications service. Added
|
||||||
|
`realm_push_notifications_enabled_end_timestamp` field to realm
|
||||||
|
data.
|
||||||
|
|
||||||
|
* [`GET /events`](/api/get-events): A `realm` update event is now sent
|
||||||
|
whenever `push_notifications_enabled` or
|
||||||
|
`push_notifications_enabled_end_timestamp` changes.
|
||||||
|
|
||||||
**Feature level 230**
|
**Feature level 230**
|
||||||
|
|
||||||
* [`GET /events`](/api/get-events): Added `has_trigger` field in
|
* [`GET /events`](/api/get-events): Added `has_trigger` field in
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 230
|
API_FEATURE_LEVEL = 231
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -229,6 +229,7 @@ export function dispatch_normal_event(event) {
|
||||||
notifications_stream_id: stream_ui_updates.update_announce_stream_option,
|
notifications_stream_id: stream_ui_updates.update_announce_stream_option,
|
||||||
org_type: noop,
|
org_type: noop,
|
||||||
private_message_policy: noop,
|
private_message_policy: noop,
|
||||||
|
push_notifications_enabled: noop,
|
||||||
send_welcome_emails: noop,
|
send_welcome_emails: noop,
|
||||||
message_content_allowed_in_email_notifications: noop,
|
message_content_allowed_in_email_notifications: noop,
|
||||||
enable_spectator_access: noop,
|
enable_spectator_access: noop,
|
||||||
|
|
|
@ -14,6 +14,7 @@ from zerver.actions.realm_settings import (
|
||||||
do_deactivate_realm,
|
do_deactivate_realm,
|
||||||
)
|
)
|
||||||
from zerver.lib.bulk_create import create_users
|
from zerver.lib.bulk_create import create_users
|
||||||
|
from zerver.lib.push_notifications import sends_notifications_directly
|
||||||
from zerver.lib.remote_server import enqueue_register_realm_with_push_bouncer_if_needed
|
from zerver.lib.remote_server import enqueue_register_realm_with_push_bouncer_if_needed
|
||||||
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
||||||
from zerver.lib.streams import ensure_stream, get_signups_stream
|
from zerver.lib.streams import ensure_stream, get_signups_stream
|
||||||
|
@ -215,6 +216,9 @@ def do_create_realm(
|
||||||
kwargs["enable_read_receipts"] = (
|
kwargs["enable_read_receipts"] = (
|
||||||
invite_required is None or invite_required is True or emails_restricted_to_domains
|
invite_required is None or invite_required is True or emails_restricted_to_domains
|
||||||
)
|
)
|
||||||
|
# Initialize this property correctly in the case that no network activity
|
||||||
|
# is required to do so correctly.
|
||||||
|
kwargs["push_notifications_enabled"] = sends_notifications_directly()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
realm = Realm(string_id=string_id, name=name, **kwargs)
|
realm = Realm(string_id=string_id, name=name, **kwargs)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from zerver.lib.message import parse_message_time_limit_setting, update_first_vi
|
||||||
from zerver.lib.retention import move_messages_to_archive
|
from zerver.lib.retention import move_messages_to_archive
|
||||||
from zerver.lib.send_email import FromAddress, send_email_to_admins
|
from zerver.lib.send_email import FromAddress, send_email_to_admins
|
||||||
from zerver.lib.sessions import delete_user_sessions
|
from zerver.lib.sessions import delete_user_sessions
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
|
||||||
from zerver.lib.upload import delete_message_attachments
|
from zerver.lib.upload import delete_message_attachments
|
||||||
from zerver.lib.user_counts import realm_user_count_by_role
|
from zerver.lib.user_counts import realm_user_count_by_role
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
|
@ -102,6 +103,50 @@ def do_set_realm_property(
|
||||||
update_users_in_full_members_system_group(realm, acting_user=acting_user)
|
update_users_in_full_members_system_group(realm, acting_user=acting_user)
|
||||||
|
|
||||||
|
|
||||||
|
def do_set_push_notifications_enabled_end_timestamp(
|
||||||
|
realm: Realm, value: Optional[int], *, acting_user: Optional[UserProfile]
|
||||||
|
) -> None:
|
||||||
|
# Variant of do_set_realm_property with a bit of extra complexity
|
||||||
|
# for the fact that we store a datetime object in the database but
|
||||||
|
# use an integer format timestamp in the API.
|
||||||
|
name = "push_notifications_enabled_end_timestamp"
|
||||||
|
old_timestamp = None
|
||||||
|
old_datetime = getattr(realm, name)
|
||||||
|
if old_datetime is not None:
|
||||||
|
old_timestamp = datetime_to_timestamp(old_datetime)
|
||||||
|
|
||||||
|
if old_timestamp == value:
|
||||||
|
return
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
new_datetime = None
|
||||||
|
if value is not None:
|
||||||
|
new_datetime = timestamp_to_datetime(value)
|
||||||
|
setattr(realm, name, new_datetime)
|
||||||
|
realm.save(update_fields=[name])
|
||||||
|
|
||||||
|
event_time = timezone_now()
|
||||||
|
RealmAuditLog.objects.create(
|
||||||
|
realm=realm,
|
||||||
|
event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
||||||
|
event_time=event_time,
|
||||||
|
acting_user=acting_user,
|
||||||
|
extra_data={
|
||||||
|
RealmAuditLog.OLD_VALUE: old_timestamp,
|
||||||
|
RealmAuditLog.NEW_VALUE: value,
|
||||||
|
"property": name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
event = dict(
|
||||||
|
type="realm",
|
||||||
|
op="update",
|
||||||
|
property=name,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
send_event(realm, event, active_user_ids(realm.id))
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic(durable=True)
|
@transaction.atomic(durable=True)
|
||||||
def do_change_realm_permission_group_setting(
|
def do_change_realm_permission_group_setting(
|
||||||
realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile]
|
realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile]
|
||||||
|
|
|
@ -39,7 +39,6 @@ from zerver.lib.muted_users import get_user_mutes
|
||||||
from zerver.lib.narrow import check_narrow_for_events, read_stop_words
|
from zerver.lib.narrow import check_narrow_for_events, read_stop_words
|
||||||
from zerver.lib.narrow_helpers import NarrowTerm
|
from zerver.lib.narrow_helpers import NarrowTerm
|
||||||
from zerver.lib.presence import get_presence_for_user, get_presences_for_realm
|
from zerver.lib.presence import get_presence_for_user, get_presences_for_realm
|
||||||
from zerver.lib.push_notifications import push_notifications_configured
|
|
||||||
from zerver.lib.realm_icon import realm_icon_url
|
from zerver.lib.realm_icon import realm_icon_url
|
||||||
from zerver.lib.realm_logo import get_realm_logo_source, get_realm_logo_url
|
from zerver.lib.realm_logo import get_realm_logo_source, get_realm_logo_url
|
||||||
from zerver.lib.scheduled_messages import get_undelivered_scheduled_messages
|
from zerver.lib.scheduled_messages import get_undelivered_scheduled_messages
|
||||||
|
@ -329,6 +328,13 @@ def fetch_initial_state_data(
|
||||||
state["zulip_plan_is_not_limited"] = realm.plan_type != Realm.PLAN_TYPE_LIMITED
|
state["zulip_plan_is_not_limited"] = realm.plan_type != Realm.PLAN_TYPE_LIMITED
|
||||||
state["upgrade_text_for_wide_organization_logo"] = str(Realm.UPGRADE_TEXT_STANDARD)
|
state["upgrade_text_for_wide_organization_logo"] = str(Realm.UPGRADE_TEXT_STANDARD)
|
||||||
|
|
||||||
|
if realm.push_notifications_enabled_end_timestamp is not None:
|
||||||
|
state["realm_push_notifications_enabled_end_timestamp"] = datetime_to_timestamp(
|
||||||
|
realm.push_notifications_enabled_end_timestamp
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
state["realm_push_notifications_enabled_end_timestamp"] = None
|
||||||
|
|
||||||
state["password_min_length"] = settings.PASSWORD_MIN_LENGTH
|
state["password_min_length"] = settings.PASSWORD_MIN_LENGTH
|
||||||
state["password_min_guesses"] = settings.PASSWORD_MIN_GUESSES
|
state["password_min_guesses"] = settings.PASSWORD_MIN_GUESSES
|
||||||
state["server_inline_image_preview"] = settings.INLINE_IMAGE_PREVIEW
|
state["server_inline_image_preview"] = settings.INLINE_IMAGE_PREVIEW
|
||||||
|
@ -345,8 +351,7 @@ def fetch_initial_state_data(
|
||||||
"event_queue_longpoll_timeout_seconds"
|
"event_queue_longpoll_timeout_seconds"
|
||||||
] = settings.EVENT_QUEUE_LONGPOLL_TIMEOUT_SECONDS
|
] = settings.EVENT_QUEUE_LONGPOLL_TIMEOUT_SECONDS
|
||||||
|
|
||||||
# TODO: Should these have the realm prefix replaced with server_?
|
# TODO: This probably belongs on the server object.
|
||||||
state["realm_push_notifications_enabled"] = push_notifications_configured()
|
|
||||||
state["realm_default_external_accounts"] = get_default_external_accounts()
|
state["realm_default_external_accounts"] = get_default_external_accounts()
|
||||||
|
|
||||||
server_default_jitsi_server_url = (
|
server_default_jitsi_server_url = (
|
||||||
|
|
|
@ -27,6 +27,7 @@ from zerver.lib.export import DATE_FIELDS, Field, Path, Record, TableData, Table
|
||||||
from zerver.lib.markdown import markdown_convert
|
from zerver.lib.markdown import markdown_convert
|
||||||
from zerver.lib.markdown import version as markdown_version
|
from zerver.lib.markdown import version as markdown_version
|
||||||
from zerver.lib.message import get_last_message_id
|
from zerver.lib.message import get_last_message_id
|
||||||
|
from zerver.lib.push_notifications import sends_notifications_directly
|
||||||
from zerver.lib.remote_server import enqueue_register_realm_with_push_bouncer_if_needed
|
from zerver.lib.remote_server import enqueue_register_realm_with_push_bouncer_if_needed
|
||||||
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
||||||
from zerver.lib.streams import render_stream_description
|
from zerver.lib.streams import render_stream_description
|
||||||
|
@ -974,6 +975,9 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
||||||
realm_properties = dict(**data["zerver_realm"][0])
|
realm_properties = dict(**data["zerver_realm"][0])
|
||||||
realm_properties["deactivated"] = True
|
realm_properties["deactivated"] = True
|
||||||
|
|
||||||
|
# Initialize whether we expect push notifications to work.
|
||||||
|
realm_properties["push_notifications_enabled"] = sends_notifications_directly()
|
||||||
|
|
||||||
with transaction.atomic(durable=True):
|
with transaction.atomic(durable=True):
|
||||||
realm = Realm(**realm_properties)
|
realm = Realm(**realm_properties)
|
||||||
if "zerver_usergroup" not in data:
|
if "zerver_usergroup" not in data:
|
||||||
|
|
|
@ -33,12 +33,20 @@ from django.utils.translation import override as override_language
|
||||||
from typing_extensions import TypeAlias, override
|
from typing_extensions import TypeAlias, override
|
||||||
|
|
||||||
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat
|
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat
|
||||||
|
from zerver.actions.realm_settings import (
|
||||||
|
do_set_push_notifications_enabled_end_timestamp,
|
||||||
|
do_set_realm_property,
|
||||||
|
)
|
||||||
from zerver.lib.avatar import absolute_avatar_url
|
from zerver.lib.avatar import absolute_avatar_url
|
||||||
from zerver.lib.emoji_utils import hex_codepoint_to_emoji
|
from zerver.lib.emoji_utils import hex_codepoint_to_emoji
|
||||||
from zerver.lib.exceptions import ErrorCode, JsonableError
|
from zerver.lib.exceptions import ErrorCode, JsonableError
|
||||||
from zerver.lib.message import access_message, huddle_users
|
from zerver.lib.message import access_message, huddle_users
|
||||||
from zerver.lib.outgoing_http import OutgoingSession
|
from zerver.lib.outgoing_http import OutgoingSession
|
||||||
from zerver.lib.remote_server import send_json_to_push_bouncer, send_to_push_bouncer
|
from zerver.lib.remote_server import (
|
||||||
|
send_json_to_push_bouncer,
|
||||||
|
send_realms_only_to_push_bouncer,
|
||||||
|
send_to_push_bouncer,
|
||||||
|
)
|
||||||
from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification
|
from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification
|
||||||
from zerver.lib.tex import change_katex_to_raw_latex
|
from zerver.lib.tex import change_katex_to_raw_latex
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
|
@ -48,6 +56,7 @@ from zerver.models import (
|
||||||
Message,
|
Message,
|
||||||
NotificationTriggers,
|
NotificationTriggers,
|
||||||
PushDeviceToken,
|
PushDeviceToken,
|
||||||
|
Realm,
|
||||||
Recipient,
|
Recipient,
|
||||||
Stream,
|
Stream,
|
||||||
UserGroup,
|
UserGroup,
|
||||||
|
@ -565,6 +574,10 @@ def uses_notification_bouncer() -> bool:
|
||||||
return settings.PUSH_NOTIFICATION_BOUNCER_URL is not None
|
return settings.PUSH_NOTIFICATION_BOUNCER_URL is not None
|
||||||
|
|
||||||
|
|
||||||
|
def sends_notifications_directly() -> bool:
|
||||||
|
return has_apns_credentials() and has_gcm_credentials() and not uses_notification_bouncer()
|
||||||
|
|
||||||
|
|
||||||
def send_notifications_to_bouncer(
|
def send_notifications_to_bouncer(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
apns_payload: Dict[str, Any],
|
apns_payload: Dict[str, Any],
|
||||||
|
@ -736,15 +749,70 @@ def push_notifications_configured() -> bool:
|
||||||
|
|
||||||
|
|
||||||
def initialize_push_notifications() -> None:
|
def initialize_push_notifications() -> None:
|
||||||
if not push_notifications_configured():
|
"""Called during startup of the push notifications worker to check
|
||||||
if settings.DEVELOPMENT and not settings.TEST_SUITE: # nocoverage
|
whether we expect mobile push notifications to work on this server
|
||||||
# Avoid unnecessary spam on development environment startup
|
and update state accordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sends_notifications_directly():
|
||||||
|
# This server sends push notifications directly. Make sure we
|
||||||
|
# are set to report to clients that push notifications are
|
||||||
|
# enabled.
|
||||||
|
for realm in Realm.objects.filter(push_notifications_enabled=False):
|
||||||
|
do_set_realm_property(realm, "push_notifications_enabled", True, acting_user=None)
|
||||||
|
do_set_push_notifications_enabled_end_timestamp(realm, None, acting_user=None)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not push_notifications_configured():
|
||||||
|
for realm in Realm.objects.filter(push_notifications_enabled=True):
|
||||||
|
do_set_realm_property(realm, "push_notifications_enabled", False, acting_user=None)
|
||||||
|
do_set_push_notifications_enabled_end_timestamp(realm, None, acting_user=None)
|
||||||
|
if settings.DEVELOPMENT and not settings.TEST_SUITE:
|
||||||
|
# Avoid unnecessary spam on development environment startup
|
||||||
|
return # nocoverage
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Mobile push notifications are not configured.\n "
|
"Mobile push notifications are not configured.\n "
|
||||||
"See https://zulip.readthedocs.io/en/latest/"
|
"See https://zulip.readthedocs.io/en/latest/"
|
||||||
"production/mobile-push-notifications.html"
|
"production/mobile-push-notifications.html"
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if uses_notification_bouncer():
|
||||||
|
# If we're using the notification bouncer, check if we can
|
||||||
|
# actually send push notifications.
|
||||||
|
|
||||||
|
try:
|
||||||
|
realms = send_realms_only_to_push_bouncer()
|
||||||
|
except Exception:
|
||||||
|
# An exception was thrown trying to ask the bouncer service whether we can send
|
||||||
|
# push notifications or not. There may be certain transient failures that we could
|
||||||
|
# ignore here, but the default explanation is that there is something wrong either
|
||||||
|
# with our credentials being corrupted or our ability to reach the bouncer service
|
||||||
|
# over the network, so we immediately move to reporting push notifications as likely not working,
|
||||||
|
# as whatever failed here is likely to also fail when trying to send a push notification.
|
||||||
|
for realm in Realm.objects.filter(push_notifications_enabled=True):
|
||||||
|
do_set_realm_property(realm, "push_notifications_enabled", False, acting_user=None)
|
||||||
|
do_set_push_notifications_enabled_end_timestamp(realm, None, acting_user=None)
|
||||||
|
logger.exception("Exception while sending realms only data to push bouncer")
|
||||||
|
return
|
||||||
|
|
||||||
|
for realm_uuid, data in realms.items():
|
||||||
|
realm = Realm.objects.get(uuid=realm_uuid)
|
||||||
|
do_set_realm_property(
|
||||||
|
realm, "push_notifications_enabled", data["can_push"], acting_user=None
|
||||||
|
)
|
||||||
|
do_set_push_notifications_enabled_end_timestamp(
|
||||||
|
realm, data["expected_end_timestamp"], acting_user=None
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning( # nocoverage
|
||||||
|
"Mobile push notifications are not fully configured.\n "
|
||||||
|
"See https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html"
|
||||||
|
)
|
||||||
|
for realm in Realm.objects.filter(push_notifications_enabled=True): # nocoverage
|
||||||
|
do_set_realm_property(realm, "push_notifications_enabled", False, acting_user=None)
|
||||||
|
do_set_push_notifications_enabled_end_timestamp(realm, None, acting_user=None)
|
||||||
|
|
||||||
|
|
||||||
def get_mobile_push_content(rendered_content: str) -> str:
|
def get_mobile_push_content(rendered_content: str) -> str:
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-11-28 14:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0491_alter_realmuserdefault_web_home_view_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="push_notifications_enabled",
|
||||||
|
field=models.BooleanField(db_index=True, default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="push_notifications_enabled_end_timestamp",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -344,6 +344,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
# bouncer.
|
# bouncer.
|
||||||
uuid = models.UUIDField(default=uuid4, unique=True)
|
uuid = models.UUIDField(default=uuid4, unique=True)
|
||||||
uuid_owner_secret = models.TextField(default=generate_realm_uuid_owner_secret)
|
uuid_owner_secret = models.TextField(default=generate_realm_uuid_owner_secret)
|
||||||
|
# Whether push notifications are working for this realm, and
|
||||||
|
# whether there is a specific date at which we expect that to
|
||||||
|
# cease to be the case.
|
||||||
|
push_notifications_enabled = models.BooleanField(default=False, db_index=True)
|
||||||
|
push_notifications_enabled_end_timestamp = models.DateTimeField(default=None, null=True)
|
||||||
|
|
||||||
date_created = models.DateTimeField(default=timezone_now)
|
date_created = models.DateTimeField(default=timezone_now)
|
||||||
demo_organization_scheduled_deletion_date = models.DateTimeField(default=None, null=True)
|
demo_organization_scheduled_deletion_date = models.DateTimeField(default=None, null=True)
|
||||||
|
@ -829,6 +834,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
name=str,
|
name=str,
|
||||||
name_changes_disabled=bool,
|
name_changes_disabled=bool,
|
||||||
private_message_policy=int,
|
private_message_policy=int,
|
||||||
|
push_notifications_enabled=bool,
|
||||||
send_welcome_emails=bool,
|
send_welcome_emails=bool,
|
||||||
user_group_edit_policy=int,
|
user_group_edit_policy=int,
|
||||||
video_chat_provider=int,
|
video_chat_provider=int,
|
||||||
|
|
|
@ -4581,6 +4581,27 @@ paths:
|
||||||
system group.
|
system group.
|
||||||
|
|
||||||
**Changes**: New in Zulip 8.0 (feature level 225).
|
**Changes**: New in Zulip 8.0 (feature level 225).
|
||||||
|
push_notifications_enabled:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Whether push notifications are enabled for this organization. Typically
|
||||||
|
`true` for Zulip Cloud and self-hosted realms that have a valid
|
||||||
|
registration for the [Mobile push notifications
|
||||||
|
service](https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html),
|
||||||
|
and `false` for self-hosted servers that do not.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 231).
|
||||||
|
Previously, this value was never updated via events.
|
||||||
|
push_notifications_enabled_end_timestamp:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
If the server expects the realm's push notifications access to end at a
|
||||||
|
definite time in the future, the time at which this is expected to happen.
|
||||||
|
Mobile clients should use this field to display warnings to users when the
|
||||||
|
indicated timestamp is near.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 231).
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
example:
|
example:
|
||||||
{
|
{
|
||||||
|
@ -14425,8 +14446,26 @@ paths:
|
||||||
Present if `realm` is present in `fetch_event_types`.
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
Whether push notifications are enabled for this organization. Typically
|
Whether push notifications are enabled for this organization. Typically
|
||||||
`false` for self-hosted servers that have not configured the
|
`true` for Zulip Cloud and self-hosted realms that have a valid
|
||||||
[Mobile push notifications service](https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html).
|
registration for the [Mobile push notifications
|
||||||
|
service](https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html),
|
||||||
|
and `false` for self-hosted servers that do not.
|
||||||
|
|
||||||
|
**Changes**: Before Zulip 8.0 (feature level 231), this incorrectly was
|
||||||
|
`true` for servers that were partly configured to use the Mobile Push
|
||||||
|
Notifications Service but not properly registered.
|
||||||
|
realm_push_notifications_enabled_end_timestamp:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
|
If the server expects the realm's push notifications access to end at a
|
||||||
|
definite time in the future, the time at which this is expected to happen.
|
||||||
|
Mobile clients should use this field to display warnings to users when the
|
||||||
|
indicated timestamp is near.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 231).
|
||||||
realm_upload_quota_mib:
|
realm_upload_quota_mib:
|
||||||
type: integer
|
type: integer
|
||||||
nullable: true
|
nullable: true
|
||||||
|
|
|
@ -76,6 +76,7 @@ from zerver.actions.realm_settings import (
|
||||||
do_change_realm_permission_group_setting,
|
do_change_realm_permission_group_setting,
|
||||||
do_change_realm_plan_type,
|
do_change_realm_plan_type,
|
||||||
do_deactivate_realm,
|
do_deactivate_realm,
|
||||||
|
do_set_push_notifications_enabled_end_timestamp,
|
||||||
do_set_realm_authentication_methods,
|
do_set_realm_authentication_methods,
|
||||||
do_set_realm_notifications_stream,
|
do_set_realm_notifications_stream,
|
||||||
do_set_realm_property,
|
do_set_realm_property,
|
||||||
|
@ -216,7 +217,7 @@ from zerver.lib.test_helpers import (
|
||||||
reset_email_visibility_to_everyone_in_zulip_realm,
|
reset_email_visibility_to_everyone_in_zulip_realm,
|
||||||
stdout_suppressed,
|
stdout_suppressed,
|
||||||
)
|
)
|
||||||
from zerver.lib.timestamp import convert_to_UTC
|
from zerver.lib.timestamp import convert_to_UTC, datetime_to_timestamp
|
||||||
from zerver.lib.topic import TOPIC_NAME
|
from zerver.lib.topic import TOPIC_NAME
|
||||||
from zerver.lib.types import ProfileDataElementUpdateDict
|
from zerver.lib.types import ProfileDataElementUpdateDict
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
|
@ -3660,6 +3661,58 @@ class RealmPropertyActionTest(BaseAction):
|
||||||
continue
|
continue
|
||||||
self.do_set_realm_user_default_setting_test(prop)
|
self.do_set_realm_user_default_setting_test(prop)
|
||||||
|
|
||||||
|
def test_do_set_push_notifications_enabled_end_timestamp(self) -> None:
|
||||||
|
realm = self.user_profile.realm
|
||||||
|
|
||||||
|
# Default value of 'push_notifications_enabled_end_timestamp' is None.
|
||||||
|
# Verify that no event is sent when the new value is the same as existing value.
|
||||||
|
new_timestamp = None
|
||||||
|
self.verify_action(
|
||||||
|
lambda: do_set_push_notifications_enabled_end_timestamp(
|
||||||
|
realm=realm,
|
||||||
|
value=new_timestamp,
|
||||||
|
acting_user=None,
|
||||||
|
),
|
||||||
|
state_change_expected=False,
|
||||||
|
num_events=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
old_datetime = timezone_now() - datetime.timedelta(days=3)
|
||||||
|
old_timestamp = datetime_to_timestamp(old_datetime)
|
||||||
|
now = timezone_now()
|
||||||
|
timestamp_now = datetime_to_timestamp(now)
|
||||||
|
|
||||||
|
realm.push_notifications_enabled_end_timestamp = old_datetime
|
||||||
|
realm.save(update_fields=["push_notifications_enabled_end_timestamp"])
|
||||||
|
|
||||||
|
event = self.verify_action(
|
||||||
|
lambda: do_set_push_notifications_enabled_end_timestamp(
|
||||||
|
realm=realm,
|
||||||
|
value=timestamp_now,
|
||||||
|
acting_user=None,
|
||||||
|
),
|
||||||
|
state_change_expected=True,
|
||||||
|
num_events=1,
|
||||||
|
)[0]
|
||||||
|
self.assertEqual(event["type"], "realm")
|
||||||
|
self.assertEqual(event["op"], "update")
|
||||||
|
self.assertEqual(event["property"], "push_notifications_enabled_end_timestamp")
|
||||||
|
self.assertEqual(event["value"], timestamp_now)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
RealmAuditLog.objects.filter(
|
||||||
|
realm=realm,
|
||||||
|
event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
||||||
|
acting_user=None,
|
||||||
|
extra_data={
|
||||||
|
RealmAuditLog.OLD_VALUE: old_timestamp,
|
||||||
|
RealmAuditLog.NEW_VALUE: timestamp_now,
|
||||||
|
"property": "push_notifications_enabled_end_timestamp",
|
||||||
|
},
|
||||||
|
).count(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserDisplayActionTest(BaseAction):
|
class UserDisplayActionTest(BaseAction):
|
||||||
def do_change_user_settings_test(self, setting_name: str) -> None:
|
def do_change_user_settings_test(self, setting_name: str) -> None:
|
||||||
|
|
|
@ -24,6 +24,7 @@ from zerver.lib.home import (
|
||||||
from zerver.lib.soft_deactivation import do_soft_deactivate_users
|
from zerver.lib.soft_deactivation import do_soft_deactivate_users
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.test_helpers import get_user_messages, queries_captured
|
from zerver.lib.test_helpers import get_user_messages, queries_captured
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
DefaultStream,
|
DefaultStream,
|
||||||
Draft,
|
Draft,
|
||||||
|
@ -178,6 +179,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_presence_disabled",
|
"realm_presence_disabled",
|
||||||
"realm_private_message_policy",
|
"realm_private_message_policy",
|
||||||
"realm_push_notifications_enabled",
|
"realm_push_notifications_enabled",
|
||||||
|
"realm_push_notifications_enabled_end_timestamp",
|
||||||
"realm_send_welcome_emails",
|
"realm_send_welcome_emails",
|
||||||
"realm_signup_notifications_stream_id",
|
"realm_signup_notifications_stream_id",
|
||||||
"realm_upload_quota_mib",
|
"realm_upload_quota_mib",
|
||||||
|
@ -1278,3 +1280,17 @@ class HomeTest(ZulipTestCase):
|
||||||
# +2 for what's already in the test DB.
|
# +2 for what's already in the test DB.
|
||||||
for draft in page_params["drafts"]:
|
for draft in page_params["drafts"]:
|
||||||
self.assertNotEqual(draft["timestamp"], base_time)
|
self.assertNotEqual(draft["timestamp"], base_time)
|
||||||
|
|
||||||
|
def test_realm_push_notifications_enabled_end_timestamp(self) -> None:
|
||||||
|
self.login("hamlet")
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
end_timestamp = timezone_now() + datetime.timedelta(days=1)
|
||||||
|
realm.push_notifications_enabled_end_timestamp = end_timestamp
|
||||||
|
realm.save()
|
||||||
|
|
||||||
|
result = self._get_home_page(stream="Denmark")
|
||||||
|
page_params = self._get_page_params(result)
|
||||||
|
self.assertEqual(
|
||||||
|
page_params["realm_push_notifications_enabled_end_timestamp"],
|
||||||
|
datetime_to_timestamp(end_timestamp),
|
||||||
|
)
|
||||||
|
|
|
@ -737,6 +737,65 @@ class PushBouncerNotificationTest(BouncerTestCase):
|
||||||
result = self.uuid_post(self.server_uuid, endpoint, payload)
|
result = self.uuid_post(self.server_uuid, endpoint, payload)
|
||||||
self.assert_json_error(result, "Invalid APNS token")
|
self.assert_json_error(result, "Invalid APNS token")
|
||||||
|
|
||||||
|
def test_initialize_push_notifications(self) -> None:
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
realm.push_notifications_enabled = False
|
||||||
|
realm.save()
|
||||||
|
|
||||||
|
from zerver.lib.push_notifications import initialize_push_notifications
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.lib.push_notifications.sends_notifications_directly", return_value=True
|
||||||
|
):
|
||||||
|
initialize_push_notifications()
|
||||||
|
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertTrue(realm.push_notifications_enabled)
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.lib.push_notifications.push_notifications_configured", return_value=False
|
||||||
|
), self.assertLogs("zerver.lib.push_notifications", level="WARNING") as warn_log:
|
||||||
|
initialize_push_notifications()
|
||||||
|
|
||||||
|
not_configured_warn_log = (
|
||||||
|
"WARNING:zerver.lib.push_notifications:"
|
||||||
|
"Mobile push notifications are not configured.\n "
|
||||||
|
"See https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html"
|
||||||
|
)
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertFalse(realm.push_notifications_enabled)
|
||||||
|
self.assertEqual(
|
||||||
|
warn_log.output[0],
|
||||||
|
not_configured_warn_log,
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.lib.push_notifications.uses_notification_bouncer", return_value=True
|
||||||
|
):
|
||||||
|
realms_response = {realm.uuid: {"can_push": True, "expected_end_timestamp": None}}
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.lib.push_notifications.send_realms_only_to_push_bouncer",
|
||||||
|
return_value=realms_response,
|
||||||
|
):
|
||||||
|
initialize_push_notifications()
|
||||||
|
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertTrue(realm.push_notifications_enabled)
|
||||||
|
self.assertEqual(realm.push_notifications_enabled_end_timestamp, None)
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.lib.push_notifications.send_realms_only_to_push_bouncer",
|
||||||
|
side_effect=Exception,
|
||||||
|
), self.assertLogs("zerver.lib.push_notifications", level="ERROR") as exception_log:
|
||||||
|
initialize_push_notifications()
|
||||||
|
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertFalse(realm.push_notifications_enabled)
|
||||||
|
self.assertIn(
|
||||||
|
"ERROR:zerver.lib.push_notifications:Exception while sending realms only data to push bouncer",
|
||||||
|
exception_log.output[0],
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_register_token_realm_uuid_belongs_to_different_server(self) -> None:
|
def test_register_token_realm_uuid_belongs_to_different_server(self) -> None:
|
||||||
|
|
|
@ -1360,6 +1360,8 @@ class RealmAPITest(ZulipTestCase):
|
||||||
|
|
||||||
def test_update_realm_properties(self) -> None:
|
def test_update_realm_properties(self) -> None:
|
||||||
for prop in Realm.property_types:
|
for prop in Realm.property_types:
|
||||||
|
# push_notifications_enabled is maintained by the server, not via the API.
|
||||||
|
if prop != "push_notifications_enabled":
|
||||||
with self.subTest(property=prop):
|
with self.subTest(property=prop):
|
||||||
self.do_test_realm_update_api(prop)
|
self.do_test_realm_update_api(prop)
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,8 @@ def update_realm(
|
||||||
authentication_methods: Optional[Dict[str, Any]] = REQ(
|
authentication_methods: Optional[Dict[str, Any]] = REQ(
|
||||||
json_validator=check_dict([]), default=None
|
json_validator=check_dict([]), default=None
|
||||||
),
|
),
|
||||||
|
# Note: push_notifications_enabled and push_notifications_enabled_end_timestamp
|
||||||
|
# are not offered here as it is maintained by the server, not via the API.
|
||||||
notifications_stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
|
notifications_stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
signup_notifications_stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
|
signup_notifications_stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
message_retention_days_raw: Optional[Union[int, str]] = REQ(
|
message_retention_days_raw: Optional[Union[int, str]] = REQ(
|
||||||
|
|
Loading…
Reference in New Issue