types: Add EditHistoryEvent and APIEditHistoryEvent types.

These types will help make iteration on this code easier.

Note that `user_id` can be null due to the fact that
edit history entries before March 2017 did not log
the user that made the edit, which was years after
supporting topic edits (discovered in test deployment
of migration on chat.zulip.org).

Co-authored-by: Lauryn Menard <lauryn.menard@gmail.com>
This commit is contained in:
Tim Abbott
2022-03-01 15:49:18 -08:00
parent 44e08c9289
commit f1e5ed91a1
5 changed files with 61 additions and 19 deletions

View File

@@ -13,6 +13,7 @@ from zulint.custom_rules import Rule, RuleList
FILES_WITH_LEGACY_SUBJECT = {
# This basically requires a big DB migration:
"zerver/lib/topic.py",
"zerver/lib/types.py",
# This is for backward compatibility.
"zerver/tests/test_legacy_subject.py",
# Other migration-related changes require extreme care.
@@ -229,7 +230,7 @@ python_rules = RuleList(
rules=[
{
"pattern": "subject|SUBJECT",
"exclude_pattern": "subject to the|email|outbox",
"exclude_pattern": "subject to the|email|outbox|edit_history_event",
"description": "avoid subject as a var",
"good_lines": ["topic_name"],
"bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"],

View File

@@ -161,7 +161,6 @@ from zerver.lib.string_validation import check_stream_name, check_stream_topic
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
from zerver.lib.timezone import canonicalize_timezone
from zerver.lib.topic import (
LEGACY_PREV_TOPIC,
ORIG_TOPIC,
RESOLVED_TOPIC_PREFIX,
TOPIC_LINKS,
@@ -174,7 +173,12 @@ from zerver.lib.topic import (
update_messages_for_topic_edit,
)
from zerver.lib.topic_mutes import add_topic_mute, get_topic_mutes, remove_topic_mute
from zerver.lib.types import ProfileDataElementValue, ProfileFieldData, UnspecifiedValue
from zerver.lib.types import (
EditHistoryEvent,
ProfileDataElementValue,
ProfileFieldData,
UnspecifiedValue,
)
from zerver.lib.upload import (
claim_attachment,
delete_avatar_image,
@@ -6695,7 +6699,7 @@ def do_update_message(
"rendering_only": False,
}
edit_history_event: Dict[str, Any] = {
edit_history_event: EditHistoryEvent = {
"user_id": user_profile.id,
"timestamp": event["edit_timestamp"],
}
@@ -6869,7 +6873,7 @@ def do_update_message(
event[ORIG_TOPIC] = orig_topic_name
event[TOPIC_NAME] = topic_name
event[TOPIC_LINKS] = topic_links(target_message.sender.realm_id, topic_name)
edit_history_event[LEGACY_PREV_TOPIC] = orig_topic_name
edit_history_event["prev_subject"] = orig_topic_name
update_edit_history(target_message, timestamp, edit_history_event)
@@ -6879,16 +6883,14 @@ def do_update_message(
assert stream_being_edited is not None
# Other messages should only get topic/stream fields in their edit history.
topic_only_edit_history_event = {
k: v
for (k, v) in edit_history_event.items()
if k
not in [
"prev_content",
"prev_rendered_content",
"prev_rendered_content_version",
]
topic_only_edit_history_event: EditHistoryEvent = {
"user_id": edit_history_event["user_id"],
"timestamp": edit_history_event["timestamp"],
}
if topic_name is not None:
topic_only_edit_history_event["prev_subject"] = edit_history_event["prev_subject"]
if new_stream is not None:
topic_only_edit_history_event["prev_stream"] = edit_history_event["prev_stream"]
messages_list = update_messages_for_topic_edit(
acting_user=user_profile,

View File

@@ -38,7 +38,7 @@ from zerver.lib.streams import get_web_public_streams_queryset
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_mutes import build_topic_mute_checker, topic_is_muted
from zerver.lib.types import DisplayRecipientT, UserDisplayRecipient
from zerver.lib.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
from zerver.models import (
MAX_TOPIC_NAME_LENGTH,
Message,
@@ -502,7 +502,8 @@ class MessageDict:
if last_edit_time is not None:
obj["last_edit_timestamp"] = datetime_to_timestamp(last_edit_time)
assert edit_history_json is not None
edit_history = orjson.loads(edit_history_json)
# Here we assume EditHistoryEvent == APIEditHistoryEvent
edit_history: List[EditHistoryEvent] = orjson.loads(edit_history_json)
obj["edit_history"] = edit_history
if Message.need_to_render_content(

View File

@@ -8,6 +8,7 @@ from sqlalchemy.sql import ColumnElement, column, func, literal
from sqlalchemy.types import Boolean, Text
from zerver.lib.request import REQ
from zerver.lib.types import EditHistoryEvent
from zerver.models import Message, Stream, UserMessage, UserProfile
# Only use these constants for events.
@@ -135,11 +136,11 @@ def user_message_exists_for_topic(
def update_edit_history(
message: Message, last_edit_time: datetime, edit_history_event: Dict[str, Any]
message: Message, last_edit_time: datetime, edit_history_event: EditHistoryEvent
) -> None:
message.last_edit_time = last_edit_time
if message.edit_history is not None:
edit_history = orjson.loads(message.edit_history)
edit_history: List[EditHistoryEvent] = orjson.loads(message.edit_history)
edit_history.insert(0, edit_history_event)
else:
edit_history = [edit_history_event]
@@ -154,7 +155,7 @@ def update_messages_for_topic_edit(
topic_name: Optional[str],
new_stream: Optional[Stream],
old_stream: Stream,
edit_history_event: Dict[str, Any],
edit_history_event: EditHistoryEvent,
last_edit_time: datetime,
) -> List[Message]:
propagate_query = Q(recipient_id=old_stream.recipient_id, subject__iexact=orig_topic_name)

View File

@@ -89,3 +89,40 @@ class UnspecifiedValue:
"""
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
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):
"""
Database format for edit history events.
"""
# Commented fields are fields we plan to add.
user_id: Optional[int]
timestamp: int
prev_stream: int
# stream: int
prev_subject: str
# prev_topic: str
# topic: str
prev_content: str
prev_rendered_content: Optional[str]
prev_rendered_content_version: Optional[int]