mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
zerver: Add endpoints and events for reminders.
There are similar to what exists for scheduled messages expect the PATCH requests which will be added later when the functionality is implemented.
This commit is contained in:
@@ -20,6 +20,17 @@ format used by the Zulip server that they are interacting with.
|
||||
|
||||
## Changes in Zulip 11.0
|
||||
|
||||
**Feature level 399**
|
||||
|
||||
* [`GET /events`](/api/get-events):
|
||||
Added `reminders` events sent to clients when a user creates
|
||||
or deletes scheduled messages.
|
||||
* [`GET /reminders`](/api/get-reminders):
|
||||
Clients can now request `/reminders` endpoint to fetch all
|
||||
scheduled reminders.
|
||||
* [`DELETE /reminders/{reminder_id}`](/api/delete-reminder):
|
||||
Clients can now delete a scheduled reminder.
|
||||
|
||||
**Feature level 398**
|
||||
|
||||
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
|
||||
|
@@ -30,6 +30,8 @@
|
||||
#### Message reminders
|
||||
|
||||
* [Create a message reminder](/api/create-message-reminder)
|
||||
* [Get reminders](/api/get-reminders)
|
||||
* [Delete a reminder](/api/delete-reminder)
|
||||
|
||||
#### Drafts
|
||||
|
||||
|
@@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
|
||||
API_FEATURE_LEVEL = 398
|
||||
API_FEATURE_LEVEL = 399
|
||||
|
||||
# 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
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import datetime
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from zerver.actions.message_send import check_message
|
||||
from zerver.actions.scheduled_messages import do_schedule_messages
|
||||
from zerver.lib.addressee import Addressee
|
||||
from zerver.lib.message import access_message
|
||||
from zerver.lib.reminders import get_reminder_formatted_content
|
||||
from zerver.models import Client, ScheduledMessage, UserProfile
|
||||
from zerver.tornado.django_api import send_event_on_commit
|
||||
|
||||
|
||||
def schedule_reminder_for_message(
|
||||
@@ -34,6 +37,22 @@ def schedule_reminder_for_message(
|
||||
[send_request],
|
||||
current_user,
|
||||
read_by_sender=False,
|
||||
skip_events=True,
|
||||
delivery_type=ScheduledMessage.REMIND,
|
||||
)[0]
|
||||
|
||||
|
||||
def notify_remove_reminder(user_profile: UserProfile, reminder_id: int) -> None:
|
||||
event = {
|
||||
"type": "reminders",
|
||||
"op": "remove",
|
||||
"reminder_id": reminder_id,
|
||||
}
|
||||
send_event_on_commit(user_profile.realm, event, [user_profile.id])
|
||||
|
||||
|
||||
@transaction.atomic(durable=True)
|
||||
def do_delete_reminder(user_profile: UserProfile, reminder: ScheduledMessage) -> None:
|
||||
assert reminder.delivery_type == ScheduledMessage.REMIND
|
||||
reminder_id = reminder.id
|
||||
reminder.delete()
|
||||
notify_remove_reminder(user_profile, reminder_id)
|
||||
|
@@ -90,6 +90,15 @@ def notify_new_scheduled_message(
|
||||
send_event_on_commit(user_profile.realm, event, [user_profile.id])
|
||||
|
||||
|
||||
def notify_new_reminder(user_profile: UserProfile, reminders: list[ScheduledMessage]) -> None:
|
||||
event = {
|
||||
"type": "reminders",
|
||||
"op": "add",
|
||||
"reminders": [reminder.to_reminder_dict() for reminder in reminders],
|
||||
}
|
||||
send_event_on_commit(user_profile.realm, event, [user_profile.id])
|
||||
|
||||
|
||||
def do_schedule_messages(
|
||||
send_message_requests: Sequence[SendMessageRequest],
|
||||
sender: UserProfile,
|
||||
@@ -137,7 +146,10 @@ def do_schedule_messages(
|
||||
scheduled_message.save(update_fields=["has_attachment"])
|
||||
|
||||
if not skip_events:
|
||||
notify_new_scheduled_message(sender, scheduled_message_objects)
|
||||
if delivery_type == ScheduledMessage.REMIND:
|
||||
notify_new_reminder(sender, scheduled_message_objects)
|
||||
else:
|
||||
notify_new_scheduled_message(sender, scheduled_message_objects)
|
||||
return [scheduled_message.id for scheduled_message, ignored in scheduled_messages]
|
||||
|
||||
|
||||
@@ -286,7 +298,6 @@ def delete_scheduled_message(user_profile: UserProfile, scheduled_message_id: in
|
||||
scheduled_message_object = access_scheduled_message(user_profile, scheduled_message_id)
|
||||
scheduled_message_id = scheduled_message_object.id
|
||||
scheduled_message_object.delete()
|
||||
|
||||
notify_remove_scheduled_message(user_profile, scheduled_message_id)
|
||||
|
||||
|
||||
|
@@ -65,6 +65,8 @@ from zerver.lib.event_types import (
|
||||
EventRealmUserRemove,
|
||||
EventRealmUserSettingsDefaultsUpdate,
|
||||
EventRealmUserUpdate,
|
||||
EventRemindersAdd,
|
||||
EventRemindersRemove,
|
||||
EventRestart,
|
||||
EventSavedSnippetsAdd,
|
||||
EventSavedSnippetsRemove,
|
||||
@@ -197,6 +199,8 @@ check_realm_linkifiers = make_checker(EventRealmLinkifiers)
|
||||
check_realm_playgrounds = make_checker(EventRealmPlaygrounds)
|
||||
check_realm_user_add = make_checker(EventRealmUserAdd)
|
||||
check_realm_user_remove = make_checker(EventRealmUserRemove)
|
||||
check_reminder_add = make_checker(EventRemindersAdd)
|
||||
check_reminder_remove = make_checker(EventRemindersRemove)
|
||||
check_restart = make_checker(EventRestart)
|
||||
check_saved_snippets_add = make_checker(EventSavedSnippetsAdd)
|
||||
check_saved_snippets_remove = make_checker(EventSavedSnippetsRemove)
|
||||
|
@@ -821,6 +821,29 @@ class EventScheduledMessagesUpdate(BaseEvent):
|
||||
scheduled_message: ScheduledMessageFields
|
||||
|
||||
|
||||
class ReminderFields(BaseModel):
|
||||
reminder_id: int
|
||||
type: Literal["private"]
|
||||
to: list[int]
|
||||
content: str
|
||||
rendered_content: str
|
||||
scheduled_delivery_timestamp: int
|
||||
failed: bool
|
||||
reminder_target_message_id: int
|
||||
|
||||
|
||||
class EventRemindersAdd(BaseEvent):
|
||||
type: Literal["reminders"]
|
||||
op: Literal["add"]
|
||||
reminders: list[ReminderFields]
|
||||
|
||||
|
||||
class EventRemindersRemove(BaseEvent):
|
||||
type: Literal["reminders"]
|
||||
op: Literal["remove"]
|
||||
reminder_id: int
|
||||
|
||||
|
||||
class BasicStreamFields(BaseModel):
|
||||
is_archived: bool
|
||||
can_administer_channel_group: int | UserGroupMembersDict
|
||||
|
@@ -51,7 +51,10 @@ from zerver.lib.onboarding_steps import get_next_onboarding_steps
|
||||
from zerver.lib.presence import get_presence_for_user, get_presences_for_realm
|
||||
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.scheduled_messages import get_undelivered_scheduled_messages
|
||||
from zerver.lib.scheduled_messages import (
|
||||
get_undelivered_reminders,
|
||||
get_undelivered_scheduled_messages,
|
||||
)
|
||||
from zerver.lib.soft_deactivation import reactivate_user_if_soft_deactivated
|
||||
from zerver.lib.sounds import get_available_notification_sounds
|
||||
from zerver.lib.stream_subscription import handle_stream_notifications_compatibility
|
||||
@@ -328,6 +331,9 @@ def fetch_initial_state_data(
|
||||
[] if user_profile is None else get_undelivered_scheduled_messages(user_profile)
|
||||
)
|
||||
|
||||
if want("reminders"):
|
||||
state["reminders"] = [] if user_profile is None else get_undelivered_reminders(user_profile)
|
||||
|
||||
if want("muted_topics") and (
|
||||
# Suppress muted_topics data for clients that explicitly
|
||||
# support user_topic. This allows clients to request both the
|
||||
|
@@ -1,6 +1,7 @@
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from zerver.lib.exceptions import ResourceNotFoundError
|
||||
from zerver.lib.markdown.fenced_code import get_unused_fence
|
||||
from zerver.lib.mention import silent_mention_syntax_for_user
|
||||
from zerver.lib.message import truncate_content
|
||||
@@ -8,6 +9,7 @@ from zerver.lib.message_cache import MessageDict
|
||||
from zerver.lib.topic_link_util import get_message_link_syntax
|
||||
from zerver.lib.url_encoding import message_link_url
|
||||
from zerver.models import Message, Stream, UserProfile
|
||||
from zerver.models.scheduled_jobs import ScheduledMessage
|
||||
|
||||
|
||||
def get_reminder_formatted_content(message: Message, current_user: UserProfile) -> str:
|
||||
@@ -46,3 +48,12 @@ def get_reminder_formatted_content(message: Message, current_user: UserProfile)
|
||||
fence=fence,
|
||||
msg_content=msg_content,
|
||||
)
|
||||
|
||||
|
||||
def access_reminder(user_profile: UserProfile, reminder_id: int) -> ScheduledMessage:
|
||||
try:
|
||||
return ScheduledMessage.objects.get(
|
||||
id=reminder_id, sender=user_profile, delivery_type=ScheduledMessage.REMIND
|
||||
)
|
||||
except ScheduledMessage.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Reminder does not exist"))
|
||||
|
@@ -3,6 +3,7 @@ from django.utils.translation import gettext as _
|
||||
from zerver.lib.exceptions import ResourceNotFoundError
|
||||
from zerver.models import ScheduledMessage, UserProfile
|
||||
from zerver.models.scheduled_jobs import (
|
||||
APIReminderDirectMessageDict,
|
||||
APIScheduledDirectMessageDict,
|
||||
APIScheduledStreamMessageDict,
|
||||
)
|
||||
@@ -12,7 +13,9 @@ def access_scheduled_message(
|
||||
user_profile: UserProfile, scheduled_message_id: int
|
||||
) -> ScheduledMessage:
|
||||
try:
|
||||
return ScheduledMessage.objects.get(id=scheduled_message_id, sender=user_profile)
|
||||
return ScheduledMessage.objects.get(
|
||||
id=scheduled_message_id, sender=user_profile, delivery_type=ScheduledMessage.SEND_LATER
|
||||
)
|
||||
except ScheduledMessage.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Scheduled message does not exist"))
|
||||
|
||||
@@ -32,3 +35,20 @@ def get_undelivered_scheduled_messages(
|
||||
scheduled_message.to_dict() for scheduled_message in scheduled_messages
|
||||
]
|
||||
return scheduled_message_dicts
|
||||
|
||||
|
||||
def get_undelivered_reminders(
|
||||
user_profile: UserProfile,
|
||||
) -> list[APIReminderDirectMessageDict]:
|
||||
reminders = ScheduledMessage.objects.filter(
|
||||
realm_id=user_profile.realm_id,
|
||||
sender=user_profile,
|
||||
# Notably, we don't require failed=False, since we will want
|
||||
# to display those to users.
|
||||
delivered=False,
|
||||
delivery_type=ScheduledMessage.REMIND,
|
||||
).order_by("scheduled_timestamp")
|
||||
reminder_dicts: list[APIReminderDirectMessageDict] = [
|
||||
reminder.to_reminder_dict() for reminder in reminders
|
||||
]
|
||||
return reminder_dicts
|
||||
|
@@ -138,6 +138,17 @@ class APIScheduledDirectMessageDict(TypedDict):
|
||||
failed: bool
|
||||
|
||||
|
||||
class APIReminderDirectMessageDict(TypedDict):
|
||||
reminder_id: int
|
||||
to: list[int]
|
||||
type: str
|
||||
content: str
|
||||
rendered_content: str
|
||||
scheduled_delivery_timestamp: int
|
||||
failed: bool
|
||||
reminder_target_message_id: int
|
||||
|
||||
|
||||
class ScheduledMessage(models.Model):
|
||||
sender = models.ForeignKey(UserProfile, on_delete=CASCADE)
|
||||
recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
|
||||
@@ -247,6 +258,21 @@ class ScheduledMessage(models.Model):
|
||||
failed=self.failed,
|
||||
)
|
||||
|
||||
def to_reminder_dict(self) -> APIReminderDirectMessageDict:
|
||||
assert self.reminder_target_message_id is not None
|
||||
recipient, recipient_type_str = get_recipient_ids(self.recipient, self.sender.id)
|
||||
assert recipient_type_str == "private"
|
||||
return APIReminderDirectMessageDict(
|
||||
reminder_id=self.id,
|
||||
to=recipient,
|
||||
type=recipient_type_str,
|
||||
content=self.content,
|
||||
rendered_content=self.rendered_content,
|
||||
scheduled_delivery_timestamp=datetime_to_timestamp(self.scheduled_timestamp),
|
||||
failed=self.failed,
|
||||
reminder_target_message_id=self.reminder_target_message_id,
|
||||
)
|
||||
|
||||
|
||||
EMAIL_TYPES = {
|
||||
"account_registered": ScheduledEmail.WELCOME,
|
||||
|
@@ -28,6 +28,9 @@ UNTESTED_GENERATED_CURL_EXAMPLES = {
|
||||
# Would need push notification bouncer set up to test the
|
||||
# generated curl example for this endpoint.
|
||||
"test-notify",
|
||||
# Having a message for a specific user available to test this endpoint
|
||||
# is tricky for testing.
|
||||
"delete-reminder",
|
||||
}
|
||||
|
||||
|
||||
|
@@ -5765,6 +5765,78 @@ paths:
|
||||
"op": "remove",
|
||||
"saved_snippet_id": 17,
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Event sent to a user's clients when a reminder is scheduled.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 399).
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/EventIdSchema"
|
||||
type:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/EventTypeSchema"
|
||||
- enum:
|
||||
- reminders
|
||||
op:
|
||||
type: string
|
||||
enum:
|
||||
- add
|
||||
reminders:
|
||||
type: array
|
||||
description: |
|
||||
An array of objects containing details of the newly created
|
||||
reminders.
|
||||
items:
|
||||
$ref: "#/components/schemas/Reminder"
|
||||
example:
|
||||
{
|
||||
"type": "reminders",
|
||||
"op": "add",
|
||||
"reminders":
|
||||
[
|
||||
{
|
||||
"reminder_id": 17,
|
||||
"type": "private",
|
||||
"to": [6],
|
||||
"content": "Hello there!",
|
||||
"rendered_content": "<p>Hello there!</p>",
|
||||
"scheduled_delivery_timestamp": 1681662420,
|
||||
"failed": false,
|
||||
"reminder_target_message_id": 42,
|
||||
},
|
||||
],
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Event sent to a user's clients when a reminder
|
||||
is deleted.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 399).
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/EventIdSchema"
|
||||
type:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/EventTypeSchema"
|
||||
- enum:
|
||||
- reminders
|
||||
op:
|
||||
type: string
|
||||
enum:
|
||||
- remove
|
||||
reminder_id:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the reminder that was deleted.
|
||||
example:
|
||||
{
|
||||
"type": "reminders",
|
||||
"op": "remove",
|
||||
"reminder_id": 17,
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
@@ -7020,6 +7092,56 @@ paths:
|
||||
"msg": "Saved snippet does not exist.",
|
||||
}
|
||||
/reminders:
|
||||
get:
|
||||
operationId: get-reminders
|
||||
tags: ["reminders"]
|
||||
summary: Get reminders
|
||||
description: |
|
||||
Fetch all [reminders](/help/schedule-a-reminder) for the
|
||||
current user.
|
||||
|
||||
Reminders are messages the user has scheduled to be sent in the
|
||||
future to themself.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 399).
|
||||
responses:
|
||||
"200":
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/JsonSuccessBase"
|
||||
- additionalProperties: false
|
||||
properties:
|
||||
result: {}
|
||||
msg: {}
|
||||
ignored_parameters_unsupported: {}
|
||||
reminders:
|
||||
type: array
|
||||
description: |
|
||||
Returns all of the current user's undelivered reminders,
|
||||
ordered by `scheduled_delivery_timestamp` (ascending).
|
||||
items:
|
||||
$ref: "#/components/schemas/Reminder"
|
||||
example:
|
||||
{
|
||||
"result": "success",
|
||||
"msg": "",
|
||||
"reminders":
|
||||
[
|
||||
{
|
||||
"reminder_id": 27,
|
||||
"to": [6],
|
||||
"type": "private",
|
||||
"content": "Hi",
|
||||
"rendered_content": "<p>Hi</p>",
|
||||
"scheduled_delivery_timestamp": 1681662420,
|
||||
"failed": false,
|
||||
"reminder_target_message_id": 42,
|
||||
},
|
||||
],
|
||||
}
|
||||
post:
|
||||
operationId: create-message-reminder
|
||||
tags: ["reminders"]
|
||||
@@ -7064,6 +7186,48 @@ paths:
|
||||
description: |
|
||||
Unique ID of the scheduled message reminder.
|
||||
example: {"msg": "", "reminder_id": 42, "result": "success"}
|
||||
|
||||
/reminders/{reminder_id}:
|
||||
delete:
|
||||
operationId: delete-reminder
|
||||
tags: ["reminders"]
|
||||
summary: Delete a reminder
|
||||
description: |
|
||||
Delete, and therefore cancel sending, a previously [scheduled
|
||||
reminder](/help/schedule-a-reminder).
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 399).
|
||||
parameters:
|
||||
- name: reminder_id
|
||||
in: path
|
||||
schema:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the reminder to delete.
|
||||
|
||||
This is different from the unique ID that the message would have
|
||||
after being sent.
|
||||
required: true
|
||||
example: 1
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/SimpleSuccess"
|
||||
"404":
|
||||
description: Not Found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/CodedError"
|
||||
- description: |
|
||||
A typical failed JSON response for when no reminder exists
|
||||
with the provided ID:
|
||||
example:
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"result": "error",
|
||||
"msg": "Reminder does not exist",
|
||||
}
|
||||
/scheduled_messages:
|
||||
get:
|
||||
operationId: get-scheduled-messages
|
||||
@@ -15875,6 +16039,17 @@ paths:
|
||||
**Changes**: New in Zulip 7.0 (feature level 179).
|
||||
items:
|
||||
$ref: "#/components/schemas/ScheduledMessage"
|
||||
reminders:
|
||||
type: array
|
||||
description: |
|
||||
Present if `reminders` is present in `fetch_event_types`.
|
||||
|
||||
An array of all undelivered reminders scheduled by the user.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 399).
|
||||
items:
|
||||
$ref: "#/components/schemas/ScheduledMessage"
|
||||
|
||||
muted_topics:
|
||||
type: array
|
||||
deprecated: true
|
||||
@@ -26272,6 +26447,71 @@ components:
|
||||
rendered_content: {}
|
||||
scheduled_delivery_timestamp: {}
|
||||
failed: {}
|
||||
Reminder:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Object containing details of the scheduled message.
|
||||
properties:
|
||||
reminder_id:
|
||||
type: integer
|
||||
description: |
|
||||
The unique ID of the reminder, which can be used to
|
||||
delete the reminder.
|
||||
|
||||
This is different from the unique ID that the message would have
|
||||
after being sent.
|
||||
type:
|
||||
type: string
|
||||
description: |
|
||||
The type of the reminder. Always set to `"private"`.
|
||||
enum:
|
||||
- private
|
||||
to:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
description: |
|
||||
Contains the ID of the user who scheduled the reminder,
|
||||
and to which the reminder will be sent.
|
||||
content:
|
||||
type: string
|
||||
description: |
|
||||
The content/body of the reminder, in text/markdown format.
|
||||
rendered_content:
|
||||
type: string
|
||||
description: |
|
||||
The content/body of the reminder rendered in HTML.
|
||||
scheduled_delivery_timestamp:
|
||||
type: integer
|
||||
description: |
|
||||
The UNIX timestamp for when the message will be sent
|
||||
by the server, in UTC seconds.
|
||||
example: 1595479019
|
||||
failed:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the server has tried to send the reminder
|
||||
and it failed to successfully send.
|
||||
|
||||
Clients that support unscheduling reminders
|
||||
should display scheduled messages with `"failed": true` with an
|
||||
indicator that the server failed to send the message at the
|
||||
scheduled time, so that the user is aware of the failure and can
|
||||
get the content of the scheduled message.
|
||||
reminder_target_message_id:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the message that the reminder is created for.
|
||||
required:
|
||||
- reminder_id
|
||||
- type
|
||||
- to
|
||||
- content
|
||||
- rendered_content
|
||||
- scheduled_delivery_timestamp
|
||||
- failed
|
||||
- reminder_target_message_id
|
||||
GroupPermissionSetting:
|
||||
description: |
|
||||
Configuration for a group permission setting specifying the groups
|
||||
|
@@ -1241,7 +1241,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||
self.login_user(user)
|
||||
|
||||
with (
|
||||
self.assert_database_query_count(46),
|
||||
self.assert_database_query_count(47),
|
||||
mock.patch("zerver.lib.events.always_want") as want_mock,
|
||||
):
|
||||
fetch_initial_state_data(user, realm=user.realm)
|
||||
@@ -1277,6 +1277,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||
realm_user_groups=2,
|
||||
realm_user_settings_defaults=1,
|
||||
recent_private_conversations=1,
|
||||
reminders=1,
|
||||
saved_snippets=1,
|
||||
scheduled_messages=1,
|
||||
starred_messages=1,
|
||||
|
@@ -223,6 +223,7 @@ class HomeTest(ZulipTestCase):
|
||||
"realm_zulip_update_announcements_stream_id",
|
||||
"realm_moderation_request_channel_id",
|
||||
"recent_private_conversations",
|
||||
"reminders",
|
||||
"saved_snippets",
|
||||
"scheduled_messages",
|
||||
"server_avatar_changes_disabled",
|
||||
@@ -284,7 +285,7 @@ class HomeTest(ZulipTestCase):
|
||||
|
||||
# Verify succeeds once logged-in
|
||||
with (
|
||||
self.assert_database_query_count(56),
|
||||
self.assert_database_query_count(57),
|
||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||
):
|
||||
result = self._get_home_page(stream="Denmark")
|
||||
@@ -592,7 +593,7 @@ class HomeTest(ZulipTestCase):
|
||||
# Verify number of queries for Realm admin isn't much higher than for normal users.
|
||||
self.login("iago")
|
||||
with (
|
||||
self.assert_database_query_count(55),
|
||||
self.assert_database_query_count(56),
|
||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||
):
|
||||
result = self._get_home_page()
|
||||
@@ -624,7 +625,7 @@ class HomeTest(ZulipTestCase):
|
||||
self._get_home_page()
|
||||
|
||||
# Then for the second page load, measure the number of queries.
|
||||
with self.assert_database_query_count(51):
|
||||
with self.assert_database_query_count(52):
|
||||
result = self._get_home_page()
|
||||
|
||||
# Do a sanity check that our new streams were in the payload.
|
||||
|
@@ -289,3 +289,58 @@ class RemindersTest(ZulipTestCase):
|
||||
self.get_dm_reminder_content(content, reminder.reminder_target_message_id),
|
||||
)
|
||||
self.assertEqual(delivered_message.date_sent, more_than_scheduled_delivery_datetime)
|
||||
|
||||
def test_delete_reminder(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
cordelia = self.example_user("cordelia")
|
||||
|
||||
response = self.api_get(hamlet, "/api/v1/reminders")
|
||||
self.assert_json_success(response)
|
||||
response_data = response.json()
|
||||
self.assertEqual(response_data["reminders"], [])
|
||||
|
||||
# Create a test message to schedule a reminder for.
|
||||
message_id = self.send_stream_message(
|
||||
hamlet,
|
||||
"Denmark",
|
||||
)
|
||||
|
||||
# Schedule a reminder for the created message.
|
||||
deliver_at = int(time.time() + 86400)
|
||||
|
||||
response = self.do_schedule_reminder(
|
||||
message_id=message_id,
|
||||
scheduled_delivery_timestamp=deliver_at,
|
||||
)
|
||||
self.assert_json_success(response)
|
||||
response_data = response.json()
|
||||
self.assertIn("reminder_id", response_data)
|
||||
reminder_id = response_data["reminder_id"]
|
||||
|
||||
# Verify that the reminder was scheduled correctly.
|
||||
reminders_response = self.api_get(hamlet, "/api/v1/reminders")
|
||||
self.assert_json_success(reminders_response)
|
||||
reminders_data = reminders_response.json()
|
||||
self.assert_length(reminders_data["reminders"], 1)
|
||||
reminder = reminders_data["reminders"][0]
|
||||
self.assertEqual(reminder["reminder_id"], reminder_id)
|
||||
self.assertEqual(reminder["reminder_target_message_id"], message_id)
|
||||
|
||||
# Test deleting the reminder with the wrong user.
|
||||
result = self.api_delete(cordelia, f"/api/v1/reminders/{reminder_id}")
|
||||
self.assert_json_error(result, "Reminder does not exist", status_code=404)
|
||||
|
||||
# Test deleting the reminder.
|
||||
result = self.client_delete(f"/json/reminders/{reminder_id}")
|
||||
self.assert_json_success(result)
|
||||
|
||||
# Verify that the reminder was deleted.
|
||||
self.assertEqual(response.status_code, 200)
|
||||
reminders_response = self.api_get(hamlet, "/api/v1/reminders")
|
||||
self.assert_json_success(reminders_response)
|
||||
reminders_data = reminders_response.json()
|
||||
self.assert_length(reminders_data["reminders"], 0)
|
||||
|
||||
# Try deleting again to trigger failure.
|
||||
result = self.client_delete(f"/json/reminders/{reminder_id}")
|
||||
self.assert_json_error(result, "Reminder does not exist", status_code=404)
|
||||
|
@@ -1,13 +1,14 @@
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from pydantic import Json
|
||||
from pydantic import Json, NonNegativeInt
|
||||
|
||||
from zerver.actions.reminders import schedule_reminder_for_message
|
||||
from zerver.actions.reminders import do_delete_reminder, schedule_reminder_for_message
|
||||
from zerver.lib.exceptions import DeliveryTimeNotInFutureError
|
||||
from zerver.lib.reminders import access_reminder
|
||||
from zerver.lib.request import RequestNotes
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import timestamp_to_datetime
|
||||
from zerver.lib.typed_endpoint import typed_endpoint
|
||||
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
|
||||
from zerver.models import UserProfile
|
||||
|
||||
|
||||
@@ -33,3 +34,15 @@ def create_reminders_message_backend(
|
||||
deliver_at,
|
||||
)
|
||||
return json_success(request, data={"reminder_id": reminder_id})
|
||||
|
||||
|
||||
@typed_endpoint
|
||||
def delete_reminder(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
reminder_id: PathOnly[NonNegativeInt],
|
||||
) -> HttpResponse:
|
||||
reminder = access_reminder(user_profile, reminder_id)
|
||||
do_delete_reminder(user_profile, reminder)
|
||||
return json_success(request)
|
||||
|
@@ -14,7 +14,10 @@ from zerver.lib.exceptions import DeliveryTimeNotInFutureError, JsonableError
|
||||
from zerver.lib.recipient_parsing import extract_direct_message_recipient_ids, extract_stream_id
|
||||
from zerver.lib.request import RequestNotes
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.scheduled_messages import get_undelivered_scheduled_messages
|
||||
from zerver.lib.scheduled_messages import (
|
||||
get_undelivered_reminders,
|
||||
get_undelivered_scheduled_messages,
|
||||
)
|
||||
from zerver.lib.timestamp import timestamp_to_datetime
|
||||
from zerver.lib.typed_endpoint import (
|
||||
ApiParamConfig,
|
||||
@@ -34,6 +37,11 @@ def fetch_scheduled_messages(request: HttpRequest, user_profile: UserProfile) ->
|
||||
)
|
||||
|
||||
|
||||
@typed_endpoint_without_parameters
|
||||
def fetch_reminders(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
return json_success(request, data={"reminders": get_undelivered_reminders(user_profile)})
|
||||
|
||||
|
||||
@typed_endpoint
|
||||
def delete_scheduled_messages(
|
||||
request: HttpRequest,
|
||||
|
@@ -164,7 +164,7 @@ from zerver.views.registration import (
|
||||
realm_register,
|
||||
signup_send_confirm,
|
||||
)
|
||||
from zerver.views.reminders import create_reminders_message_backend
|
||||
from zerver.views.reminders import create_reminders_message_backend, delete_reminder
|
||||
from zerver.views.report import report_csp_violations
|
||||
from zerver.views.saved_snippets import (
|
||||
create_saved_snippet,
|
||||
@@ -175,6 +175,7 @@ from zerver.views.saved_snippets import (
|
||||
from zerver.views.scheduled_messages import (
|
||||
create_scheduled_message_backend,
|
||||
delete_scheduled_messages,
|
||||
fetch_reminders,
|
||||
fetch_scheduled_messages,
|
||||
update_scheduled_message_backend,
|
||||
)
|
||||
@@ -378,7 +379,11 @@ v1_api_and_json_patterns = [
|
||||
DELETE=delete_saved_snippet,
|
||||
PATCH=edit_saved_snippet,
|
||||
),
|
||||
rest_path("reminders", POST=create_reminders_message_backend),
|
||||
rest_path("reminders", GET=fetch_reminders, POST=create_reminders_message_backend),
|
||||
rest_path(
|
||||
"reminders/<int:reminder_id>",
|
||||
DELETE=delete_reminder,
|
||||
),
|
||||
rest_path(
|
||||
"scheduled_messages", GET=fetch_scheduled_messages, POST=create_scheduled_message_backend
|
||||
),
|
||||
|
Reference in New Issue
Block a user