mirror of https://github.com/zulip/zulip.git
api: Add additional fields to `edit_history` entries.
Since we've changed the database to contain these new fields, we just need to stop dropping them in the API code. This also changes the public API to match the database format again by removing `prev_subject` from edit history API. Adds an API changelog feature update for the renamed `prev_subject` field (to `prev_topic`) and new fields (`topic` and `stream`) in the message `edit_history`. Also, documents said `edit_history` in the `MessagesBase` schema in the api documentation, which is used by the `/get-messages`, `/get-events` and `/zulip-outgoing-webhooks` endpoints. Fixes #21076. Co-authored-by: Lauryn Menard <lauryn.menard@gmail.com>
This commit is contained in:
parent
4e91d03d56
commit
072051f81e
|
@ -20,6 +20,21 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 5.0
|
## Changes in Zulip 5.0
|
||||||
|
|
||||||
|
**Feature level 118**
|
||||||
|
|
||||||
|
* [`GET /messages`](/api/get-messages), [`GET
|
||||||
|
/events`](/api/get-events): Improved the format of the
|
||||||
|
`edit_history` object within message objects. Entries for stream
|
||||||
|
edits now include a both a `prev_stream` and `stream` field to
|
||||||
|
indicate the previous and current stream IDs. Entries for topic
|
||||||
|
edits now include both a `prev_topic` and `topic` field to indicate
|
||||||
|
the previous and current topic, replacing the `prev_subject`
|
||||||
|
field. These changes substantially simplify client complexity for
|
||||||
|
processing historical message edits.
|
||||||
|
|
||||||
|
* [`GET messages/{message_id}/history`](/api/get-message-history):
|
||||||
|
Added `stream` field to message history `snapshot` indicating
|
||||||
|
the updated stream ID of messages moved to a new stream.
|
||||||
|
|
||||||
**Feature level 117**
|
**Feature level 117**
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in templates/zerver/api/changelog.md, as well as
|
# new level means in templates/zerver/api/changelog.md, as well as
|
||||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 117
|
API_FEATURE_LEVEL = 118
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -38,12 +38,7 @@ from zerver.lib.streams import get_web_public_streams_queryset
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.topic import DB_TOPIC_NAME, MESSAGE__TOPIC, TOPIC_LINKS, TOPIC_NAME
|
from zerver.lib.topic import DB_TOPIC_NAME, MESSAGE__TOPIC, TOPIC_LINKS, TOPIC_NAME
|
||||||
from zerver.lib.topic_mutes import build_topic_mute_checker, topic_is_muted
|
from zerver.lib.topic_mutes import build_topic_mute_checker, topic_is_muted
|
||||||
from zerver.lib.types import (
|
from zerver.lib.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
|
||||||
APIEditHistoryEvent,
|
|
||||||
DisplayRecipientT,
|
|
||||||
EditHistoryEvent,
|
|
||||||
UserDisplayRecipient,
|
|
||||||
)
|
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
MAX_TOPIC_NAME_LENGTH,
|
MAX_TOPIC_NAME_LENGTH,
|
||||||
Message,
|
Message,
|
||||||
|
@ -507,25 +502,7 @@ class MessageDict:
|
||||||
if last_edit_time is not None:
|
if last_edit_time is not None:
|
||||||
obj["last_edit_timestamp"] = datetime_to_timestamp(last_edit_time)
|
obj["last_edit_timestamp"] = datetime_to_timestamp(last_edit_time)
|
||||||
assert edit_history_json is not None
|
assert edit_history_json is not None
|
||||||
raw_edit_history: List[EditHistoryEvent] = orjson.loads(edit_history_json)
|
edit_history: List[EditHistoryEvent] = orjson.loads(edit_history_json)
|
||||||
edit_history: List[APIEditHistoryEvent] = []
|
|
||||||
for edit_history_event in raw_edit_history:
|
|
||||||
# Drop fields we're not yet ready to have appear in the API
|
|
||||||
if "prev_topic" in edit_history_event:
|
|
||||||
# The prev_subject field has been renamed in the
|
|
||||||
# database, but not the API.
|
|
||||||
edit_history_event["prev_subject"] = edit_history_event["prev_topic"] # type: ignore # Temporary type-checking violation
|
|
||||||
|
|
||||||
# New fields not consistently available, and thus
|
|
||||||
# intentionally not exposed to the API.
|
|
||||||
if "prev_topic" in edit_history_event:
|
|
||||||
del edit_history_event["prev_topic"]
|
|
||||||
if "stream" in edit_history_event:
|
|
||||||
del edit_history_event["stream"]
|
|
||||||
if "topic" in edit_history_event:
|
|
||||||
del edit_history_event["topic"]
|
|
||||||
|
|
||||||
edit_history.append(edit_history_event) # type: ignore # Temporary type-checking violation
|
|
||||||
obj["edit_history"] = edit_history
|
obj["edit_history"] = edit_history
|
||||||
|
|
||||||
if Message.need_to_render_content(
|
if Message.need_to_render_content(
|
||||||
|
|
|
@ -91,32 +91,11 @@ class UnspecifiedValue:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class APIEditHistoryEvent(TypedDict, total=False):
|
|
||||||
"""Format of legacy edit history events in the API. Contains legacy
|
|
||||||
fields like LEGACY_PREV_TOPIC that we intend to remove from the
|
|
||||||
API eventually.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Commented fields are fields we plan to add.
|
|
||||||
user_id: Optional[int]
|
|
||||||
timestamp: int
|
|
||||||
prev_stream: int
|
|
||||||
# stream: int
|
|
||||||
# TODO: Remove prev_subject from the API.
|
|
||||||
prev_subject: str
|
|
||||||
# prev_topic: str
|
|
||||||
# topic: str
|
|
||||||
prev_content: str
|
|
||||||
prev_rendered_content: Optional[str]
|
|
||||||
prev_rendered_content_version: Optional[int]
|
|
||||||
|
|
||||||
|
|
||||||
class EditHistoryEvent(TypedDict, total=False):
|
class EditHistoryEvent(TypedDict, total=False):
|
||||||
"""
|
"""
|
||||||
Database format for edit history events.
|
Database format for edit history events.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Commented fields are fields we plan to add.
|
|
||||||
user_id: Optional[int]
|
user_id: Optional[int]
|
||||||
timestamp: int
|
timestamp: int
|
||||||
prev_stream: int
|
prev_stream: int
|
||||||
|
|
|
@ -4963,9 +4963,8 @@ paths:
|
||||||
`topic`, `content`, `rendered_content`, `timestamp` and `user_id`. This
|
`topic`, `content`, `rendered_content`, `timestamp` and `user_id`. This
|
||||||
snapshot will be the only one present if the message has never been edited.
|
snapshot will be the only one present if the message has never been edited.
|
||||||
|
|
||||||
Also note that if a message's content was edited (but not the topic)
|
Also note that each snapshot object will only contain additional data for the
|
||||||
or the topic was edited (but not the content), the snapshot object
|
modified fields for that particular edit (e.g. if only the topic or stream
|
||||||
will only contain data for the modified fields (e.g. if only the topic
|
|
||||||
was edited, `prev_content`, `prev_rendered_content`, and
|
was edited, `prev_content`, `prev_rendered_content`, and
|
||||||
`content_html_diff` will not appear).
|
`content_html_diff` will not appear).
|
||||||
responses:
|
responses:
|
||||||
|
@ -4990,51 +4989,80 @@ paths:
|
||||||
topic:
|
topic:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the topic for the message.
|
The topic of the message immediately
|
||||||
|
after this edit event.
|
||||||
prev_topic:
|
prev_topic:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the topic for the message before being edited.
|
Only present if message's topic was edited.
|
||||||
|
|
||||||
|
The topic of the message immediately
|
||||||
|
prior to this edit event.
|
||||||
|
stream:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Only present if message's stream was edited.
|
||||||
|
|
||||||
|
The ID of the stream containing the message
|
||||||
|
immediately after this edit event.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 5.0 (feature level 118).
|
||||||
prev_stream:
|
prev_stream:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
Only present if message's stream was edited.
|
Only present if message's stream was edited.
|
||||||
|
|
||||||
The stream ID of the message prior to being edited.
|
The ID of the stream containing the message immediately
|
||||||
|
prior to this edit event.
|
||||||
content:
|
content:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the body of the message.
|
The raw Markdown content of the message
|
||||||
|
immediately after this edit event.
|
||||||
rendered_content:
|
rendered_content:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the already rendered, HTML version of `content`.
|
The rendered HTML representation of `content`.
|
||||||
prev_content:
|
prev_content:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the body of the message before being edited.
|
Only present if message's content was edited.
|
||||||
|
|
||||||
|
The raw Markdown content of the message immediately
|
||||||
|
prior to this edit event.
|
||||||
prev_rendered_content:
|
prev_rendered_content:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
the already rendered, HTML version of
|
Only present if message's content was edited.
|
||||||
`prev_content`.
|
|
||||||
|
The rendered HTML representation of `prev_content`.
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
nullable: true
|
||||||
description: |
|
description: |
|
||||||
the ID of the user that made the edit.
|
The ID of the user that made the edit.
|
||||||
|
|
||||||
|
Will be null only for edit history
|
||||||
|
events predating March 2017.
|
||||||
|
|
||||||
|
Clients can display edit history events where this
|
||||||
|
is null as modified by either the sender (for content
|
||||||
|
edits) or an unknown user (for topic edits).
|
||||||
content_html_diff:
|
content_html_diff:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
an HTML diff between this version of the message
|
Only present if message's content was edited.
|
||||||
|
|
||||||
|
An HTML diff between this version of the message
|
||||||
and the previous one.
|
and the previous one.
|
||||||
timestamp:
|
timestamp:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
the UNIX timestamp for this edit.
|
The UNIX timestamp for this edit.
|
||||||
description: |
|
description: |
|
||||||
A chronologically sorted array of `snapshot`
|
A chronologically sorted, oldest to newest, array
|
||||||
objects, each one with the values of the
|
of `snapshot` objects, each one with the values of
|
||||||
message after the edit.
|
the message after the edit.
|
||||||
example:
|
example:
|
||||||
{
|
{
|
||||||
"message_history":
|
"message_history":
|
||||||
|
@ -14625,6 +14653,106 @@ components:
|
||||||
Data on the recipient of the message;
|
Data on the recipient of the message;
|
||||||
either the name of a stream or a dictionary containing basic data on
|
either the name of a stream or a dictionary containing basic data on
|
||||||
the users who received the message.
|
the users who received the message.
|
||||||
|
edit_history:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
prev_content:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Only present if message's content was edited.
|
||||||
|
|
||||||
|
The content of the message immediately prior to this
|
||||||
|
edit event.
|
||||||
|
prev_rendered_content:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Only present if message's content was edited.
|
||||||
|
|
||||||
|
The rendered HTML representation of `prev_content`.
|
||||||
|
prev_rendered_content_version:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Only present if message's content was edited.
|
||||||
|
|
||||||
|
The Markdown processor version number for the message
|
||||||
|
immediately prior to this edit event.
|
||||||
|
prev_stream:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Only present if message's stream was edited.
|
||||||
|
|
||||||
|
The stream ID of the message immediately prior to this
|
||||||
|
edit event.
|
||||||
|
prev_topic:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Only present if message's topic was edited.
|
||||||
|
|
||||||
|
The topic of the message immediately prior to this
|
||||||
|
edit event.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 5.0 (feature level 118).
|
||||||
|
Previously, this field was called `prev_subject`;
|
||||||
|
clients are recommended to rename `prev_subject` to
|
||||||
|
`prev_topic` if present for compatibility with
|
||||||
|
older Zulip servers.
|
||||||
|
stream:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Only present if message's stream was edited.
|
||||||
|
|
||||||
|
The ID of the stream containing the message
|
||||||
|
immediately after this edit event.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 5.0 (feature level 118).
|
||||||
|
timestamp:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
The UNIX timestamp for the edit.
|
||||||
|
topic:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Only present if message's topic was edited.
|
||||||
|
|
||||||
|
The topic of the message immediately after this edit event.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 5.0 (feature level 118).
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The ID of the user that made the edit.
|
||||||
|
|
||||||
|
Will be null only for edit history
|
||||||
|
events predating March 2017.
|
||||||
|
|
||||||
|
Clients can display edit history events where this
|
||||||
|
is null as modified by either the sender (for content
|
||||||
|
edits) or an unknown user (for topic edits).
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
|
- timestamp
|
||||||
|
description: |
|
||||||
|
An array of objects, with each object documenting the
|
||||||
|
changes made in a previous edit made to the the message,
|
||||||
|
ordered chronologically from most recent to least recent
|
||||||
|
edit.
|
||||||
|
|
||||||
|
Not present if the message has never been edited or if the realm has
|
||||||
|
[disabled viewing of message edit history][disable-edit-history].
|
||||||
|
|
||||||
|
Every object will contain `user_id` and `timestamp`.
|
||||||
|
|
||||||
|
The other fields are optional, and will be present or not
|
||||||
|
depending on which of the stream, topic, and message
|
||||||
|
content were modified in the edit event. For example, if
|
||||||
|
only the topic was edited, only `prev_topic` and `topic`
|
||||||
|
will be present in addition to `user_id` and `timestamp`.
|
||||||
|
|
||||||
|
[disable-edit-history]: /help/disable-message-edit-history
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -73,12 +73,11 @@ class EditMessageTestCase(ZulipTestCase):
|
||||||
msg.sender_id,
|
msg.sender_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: uncomment assertion when edit_history fields in API match fields in database.
|
if msg.edit_history:
|
||||||
# if msg.edit_history:
|
self.assertEqual(
|
||||||
# self.assertEqual(
|
fetch_message_dict["edit_history"],
|
||||||
# fetch_message_dict["edit_history"],
|
orjson.loads(msg.edit_history),
|
||||||
# orjson.loads(msg.edit_history),
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
def prepare_move_topics(
|
def prepare_move_topics(
|
||||||
self,
|
self,
|
||||||
|
@ -879,14 +878,13 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
expected_entries.add("content_html_diff")
|
expected_entries.add("content_html_diff")
|
||||||
if i in {0, 3}:
|
if i in {0, 3}:
|
||||||
expected_entries.add("prev_stream")
|
expected_entries.add("prev_stream")
|
||||||
# TODO: uncomment when stream field is added to API
|
expected_entries.add("stream")
|
||||||
# expected_entries.add("stream")
|
|
||||||
i += 1
|
i += 1
|
||||||
self.assertEqual(expected_entries, set(entry.keys()))
|
self.assertEqual(expected_entries, set(entry.keys()))
|
||||||
self.assert_length(message_history, 7)
|
self.assert_length(message_history, 7)
|
||||||
self.assertEqual(message_history[0]["topic"], "topic 4")
|
self.assertEqual(message_history[0]["topic"], "topic 4")
|
||||||
self.assertEqual(message_history[0]["prev_topic"], "topic 3")
|
self.assertEqual(message_history[0]["prev_topic"], "topic 3")
|
||||||
# self.assertEqual(message_history[0]["stream"], stream_3.id)
|
self.assertEqual(message_history[0]["stream"], stream_3.id)
|
||||||
self.assertEqual(message_history[0]["prev_stream"], stream_2.id)
|
self.assertEqual(message_history[0]["prev_stream"], stream_2.id)
|
||||||
self.assertEqual(message_history[0]["content"], "content 4")
|
self.assertEqual(message_history[0]["content"], "content 4")
|
||||||
|
|
||||||
|
@ -900,7 +898,7 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
self.assertEqual(message_history[2]["prev_content"], "content 2")
|
self.assertEqual(message_history[2]["prev_content"], "content 2")
|
||||||
|
|
||||||
self.assertEqual(message_history[3]["topic"], "topic 2")
|
self.assertEqual(message_history[3]["topic"], "topic 2")
|
||||||
# self.assertEqual(message_history[3]["stream"], stream_2.id)
|
self.assertEqual(message_history[3]["stream"], stream_2.id)
|
||||||
self.assertEqual(message_history[3]["prev_stream"], stream_1.id)
|
self.assertEqual(message_history[3]["prev_stream"], stream_1.id)
|
||||||
self.assertEqual(message_history[3]["content"], "content 2")
|
self.assertEqual(message_history[3]["content"], "content 2")
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from zerver.lib.message import access_message, access_web_public_message
|
||||||
from zerver.lib.request import REQ, RequestNotes, has_request_variables
|
from zerver.lib.request import REQ, RequestNotes, 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
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.topic import LEGACY_PREV_TOPIC, REQ_topic
|
from zerver.lib.topic import REQ_topic
|
||||||
from zerver.lib.types import EditHistoryEvent, FormattedEditHistoryEvent
|
from zerver.lib.types import EditHistoryEvent, FormattedEditHistoryEvent
|
||||||
from zerver.lib.validator import check_bool, check_string_in, to_non_negative_int
|
from zerver.lib.validator import check_bool, check_string_in, to_non_negative_int
|
||||||
from zerver.models import Message, UserProfile
|
from zerver.models import Message, UserProfile
|
||||||
|
@ -70,7 +70,7 @@ def fill_edit_history_entries(
|
||||||
|
|
||||||
if "prev_stream" in edit_history_event:
|
if "prev_stream" in edit_history_event:
|
||||||
formatted_entry["prev_stream"] = edit_history_event["prev_stream"]
|
formatted_entry["prev_stream"] = edit_history_event["prev_stream"]
|
||||||
# TODO: Include current stream here.
|
formatted_entry["stream"] = edit_history_event["stream"]
|
||||||
|
|
||||||
formatted_edit_history.append(formatted_entry)
|
formatted_edit_history.append(formatted_entry)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue