Repository: zulip/zulip

Issue:
Moving a topic partially
I am very grateful for the "Move topic" option. But there is a little interface issue:
Sometimes a conversation drifts off the original topic and a moderator takes the liberty to move that part out to another topic. Zulip then says "This topic was moved to ...", which is incorrect: The topic is still very much alive.
So, when moving only part of a topic I would suggest to either display "Part of this topic was moved to ..." or better "A conversation was moved to ...".

Furthermore (maybe this is already done though), when moving a whole topic, the original should be marked resolved.
Moving a topic partially
I am very grateful for the "Move topic" option. But there is a little interface issue:
Sometimes a conversation drifts off the original topic and a moderator takes the liberty to move that part out to another topic. Zulip then says "This topic was moved to ...", which is incorrect: The topic is still very much alive.
So, when moving only part of a topic I would suggest to either display "Part of this topic was moved to ..." or better "A conversation was moved to ...".

Furthermore (maybe this is already done though), when moving a whole topic, the original should be marked resolved.


Discussion:
Thanks for the report!

It's a checkbox option for whether to send the "topic was moved" notification -- so the person doing the move gets to choice whether to trigger a "topic was moved" notice.  I think it makes sense to change the text of the notice when moving only one message, and possibly in a different way when moving a subset of a topic, e.g. "3 messages from this topic were moved to ...".  @alya thoughts?  Might be a good chat.zulip.org discussion.

> Furthermore (maybe this is already done though), when moving a whole topic, the original should be marked resolved.

This doesn't make sense in our model; "resolves topics" are ones with a name starting with a checkmark.
Hello @zulip/server-message-view members, this issue was labeled with the "area: message-editing" label, so you may want to check it out!

<!-- areaLabelAddition -->

Thanks for the feedback! Yeah, I think something like "3 messages from this topic were moved to..." seems good; I can kick off a discussion.
Based on [discussion on CZO](https://chat.zulip.org/#narrow/stream/101-design/topic/part.20of.20a.20topic.20moved), to solve this issue we should change the text to the following format when only part of the topic is moved:

> 3 messages from this topic were moved to ... by ... .

When all messages are moved, we should reorder the message to be:

> This topic was moved to ... by ... .
@zulipbot claim
Welcome to Zulip, @victorknox! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:

- Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
- [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!

@victorknox I saw the questions you posted on chat.zulip.org. Please take a look at the [Zulip contributor guide](https://zulip.readthedocs.io/en/latest/overview/contributing.html) to get started, and keep in mind the following guideline:

> Before you claim an issue, you should be confident that you will be able to tackle it effectively.

I will go ahead and unassign this issue, and you should feel free to re-claim it once you have figured out how to approach it (or pick a different one if you prefer).
@zulipbot claim

Welcome to Zulip, @Shubh0405! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:

- Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
- [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!

Thanks for the report!

It's a checkbox option for whether to send the "topic was moved" notification -- so the person doing the move gets to choice whether to trigger a "topic was moved" notice.  I think it makes sense to change the text of the notice when moving only one message, and possibly in a different way when moving a subset of a topic, e.g. "3 messages from this topic were moved to ...".  @alya thoughts?  Might be a good chat.zulip.org discussion.

> Furthermore (maybe this is already done though), when moving a whole topic, the original should be marked resolved.

This doesn't make sense in our model; "resolves topics" are ones with a name starting with a checkmark.
Hello @zulip/server-message-view members, this issue was labeled with the "area: message-editing" label, so you may want to check it out!

<!-- areaLabelAddition -->

Thanks for the feedback! Yeah, I think something like "3 messages from this topic were moved to..." seems good; I can kick off a discussion.
Based on [discussion on CZO](https://chat.zulip.org/#narrow/stream/101-design/topic/part.20of.20a.20topic.20moved), to solve this issue we should change the text to the following format when only part of the topic is moved:

> 3 messages from this topic were moved to ... by ... .

When all messages are moved, we should reorder the message to be:

> This topic was moved to ... by ... .
@zulipbot claim
Welcome to Zulip, @victorknox! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:

- Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
- [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!

@victorknox I saw the questions you posted on chat.zulip.org. Please take a look at the [Zulip contributor guide](https://zulip.readthedocs.io/en/latest/overview/contributing.html) to get started, and keep in mind the following guideline:

> Before you claim an issue, you should be confident that you will be able to tackle it effectively.

I will go ahead and unassign this issue, and you should feel free to re-claim it once you have figured out how to approach it (or pick a different one if you prefer).
@zulipbot claim

Welcome to Zulip, @Shubh0405! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:

- Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
- [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!


Resolving patch:
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -105,6 +105,7 @@
     MessageDict,
     SendMessageRequest,
     access_message,
+    bulk_access_messages,
     get_last_message_id,
     normalize_body,
     render_markdown,
@@ -167,6 +168,7 @@
     TOPIC_NAME,
     filter_by_exact_message_topic,
     filter_by_topic_name_via_message,
+    messages_for_topic,
     save_message_for_edit_use_case,
     update_edit_history,
     update_messages_for_topic_edit,
@@ -6488,6 +6490,7 @@ def send_message_moved_breadcrumbs(
     new_stream: Stream,
     new_topic: Optional[str],
     new_thread_notification_string: Optional[str],
+    changed_messages_count: int,
 ) -> None:
     # Since moving content between streams is highly disruptive,
     # it's worth adding a couple tombstone messages showing what
@@ -6510,6 +6513,7 @@ def send_message_moved_breadcrumbs(
                 new_thread_notification_string.format(
                     old_location=old_topic_link,
                     user=user_mention,
+                    changed_messages_count=changed_messages_count,
                 ),
             )
 
@@ -6523,6 +6527,7 @@ def send_message_moved_breadcrumbs(
                 old_thread_notification_string.format(
                     user=user_mention,
                     new_location=new_topic_link,
+                    changed_messages_count=changed_messages_count,
                 ),
             )
 
@@ -7017,17 +7022,65 @@ def user_info(um: UserMessage) -> Dict[str, Any]:
 
     if len(changed_messages) > 0 and new_stream is not None and stream_being_edited is not None:
         # Notify users that the topic was moved.
+        changed_messages_count = len(changed_messages)
+
+        if propagate_mode == "change_all":
+            moved_all_visible_messages = True
+        else:
+            # With other propagate modes, if the user in fact moved
+            # all messages in the stream, we want to explain it was a
+            # full-topic move.
+            #
+            # For security model reasons, we don't want to allow a
+            # user to take any action that would leak information
+            # about older messages they cannot access (E.g. the only
+            # remaining messages are in a stream without shared
+            # history). The bulk_access_messages call below addresses
+            # that concern.
+            #
+            # bulk_access_messages is inefficient for this task, since
+            # we just want to do the exists() version of this
+            # query. But it's nice to reuse code, and this bulk
+            # operation is likely cheaper than a `GET /messages`
+            # unless the topic has thousands of messages of history.
+            unmoved_messages = messages_for_topic(
+                stream_being_edited.recipient_id,
+                orig_topic_name,
+            )
+            visible_unmoved_messages = bulk_access_messages(
+                user_profile, unmoved_messages, stream=stream_being_edited
+            )
+            moved_all_visible_messages = len(visible_unmoved_messages) == 0
+
         old_thread_notification_string = None
         if send_notification_to_old_thread:
-            old_thread_notification_string = gettext_lazy(
-                "This topic was moved by {user} to {new_location}"
-            )
+            if moved_all_visible_messages:
+                old_thread_notification_string = gettext_lazy(
+                    "This topic was moved to {new_location} by {user}."
+                )
+            elif changed_messages_count == 1:
+                old_thread_notification_string = gettext_lazy(
+                    "A message was moved from this topic to {new_location} by {user}."
+                )
+            else:
+                old_thread_notification_string = gettext_lazy(
+                    "{changed_messages_count} messages were moved from this topic to {new_location} by {user}."
+                )
 
         new_thread_notification_string = None
         if send_notification_to_new_thread:
-            new_thread_notification_string = gettext_lazy(
-                "This topic was moved here from {old_location} by {user}"
-            )
+            if moved_all_visible_messages:
+                new_thread_notification_string = gettext_lazy(
+                    "This topic was moved here from {old_location} by {user}."
+                )
+            elif changed_messages_count == 1:
+                new_thread_notification_string = gettext_lazy(
+                    "A message was moved here from {old_location} by {user}."
+                )
+            else:
+                new_thread_notification_string = gettext_lazy(
+                    "{changed_messages_count} messages were moved here from {old_location} by {user}."
+                )
 
         send_message_moved_breadcrumbs(
             user_profile,
@@ -7037,6 +7090,7 @@ def user_info(um: UserMessage) -> Dict[str, Any]:
             new_stream,
             topic_name,
             new_thread_notification_string,
+            changed_messages_count,
         )
 
     if (


Test patch:
diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py
--- a/zerver/tests/test_message_edit.py
+++ b/zerver/tests/test_message_edit.py
@@ -117,9 +117,7 @@ def test_edit_message_no_changes(self) -> None:
         )
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
-            {
-                "message_id": msg_id,
-            },
+            {},
         )
         self.assert_json_error(result, "Nothing to change")
 
@@ -134,7 +132,6 @@ def test_move_message_cant_move_private_message(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": verona.id,
             },
         )
@@ -150,7 +147,6 @@ def test_private_message_edit_topic(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "topic": "Should not exist",
             },
         )
@@ -189,7 +185,6 @@ def test_edit_message_no_topic(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "topic": " ",
             },
         )
@@ -203,7 +198,6 @@ def test_edit_message_invalid_topic(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "topic": "editing\nfun",
             },
         )
@@ -217,7 +211,6 @@ def test_move_message_to_stream_with_content(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "content": "Not allowed",
@@ -243,7 +236,6 @@ def test_edit_submessage(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "content": "/poll Games?\nYES\nNO\nMaybe",
             },
         )
@@ -297,7 +289,6 @@ def test_save_message(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": "after edit",
             },
         )
@@ -307,7 +298,6 @@ def test_save_message(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "topic": "edited",
             },
         )
@@ -459,7 +449,6 @@ def test_edit_message_no_permission(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": "content after edit",
             },
         )
@@ -473,7 +462,6 @@ def test_edit_message_no_content(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": " ",
             },
         )
@@ -498,7 +486,6 @@ def test_edit_message_history_disabled(self) -> None:
         result_1 = self.client_patch(
             f"/json/messages/{msg_id_1}",
             {
-                "message_id": msg_id_1,
                 "content": new_content_1,
             },
         )
@@ -531,7 +518,6 @@ def test_edit_message_history(self) -> None:
         result_1 = self.client_patch(
             f"/json/messages/{msg_id_1}",
             {
-                "message_id": msg_id_1,
                 "content": new_content_1,
             },
         )
@@ -573,7 +559,6 @@ def test_edit_message_history(self) -> None:
         result_2 = self.client_patch(
             f"/json/messages/{msg_id_2}",
             {
-                "message_id": msg_id_2,
                 "content": new_content_2,
             },
         )
@@ -625,7 +610,6 @@ def test_empty_message_edit(self) -> None:
             self.client_patch(
                 "/json/messages/" + str(msg_id),
                 {
-                    "message_id": msg_id,
                     "content": "We will edit this to also render as empty.",
                 },
             )
@@ -654,7 +638,6 @@ def test_edit_link(self) -> None:
         result_1 = self.client_patch(
             f"/json/messages/{msg_id_1}",
             {
-                "message_id": msg_id_1,
                 "content": new_content_1,
             },
         )
@@ -732,7 +715,6 @@ def test_edit_cases(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": "content 2",
             },
         )
@@ -754,7 +736,6 @@ def test_edit_cases(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "topic": "topic 2",
             },
         )
@@ -767,7 +748,6 @@ def test_edit_cases(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": "content 3",
                 "topic": "topic 3",
             },
@@ -792,7 +772,6 @@ def test_edit_cases(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "content": "content 4",
             },
         )
@@ -805,7 +784,6 @@ def test_edit_cases(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "topic": "topic 4",
             },
         )
@@ -1168,7 +1146,6 @@ def notify(user_id: int) -> Dict[str, Any]:
         result = self.client_patch(
             f"/json/messages/{message_id}",
             {
-                "message_id": message_id,
                 "content": "Hello @**everyone**",
             },
         )
@@ -1210,7 +1187,6 @@ def test_wildcard_mention_restrictions_when_editing(self) -> None:
             result = self.client_patch(
                 "/json/messages/" + str(message_id),
                 {
-                    "message_id": message_id,
                     "content": "Hello @**everyone**",
                 },
             )
@@ -1222,7 +1198,6 @@ def test_wildcard_mention_restrictions_when_editing(self) -> None:
             result = self.client_patch(
                 "/json/messages/" + str(message_id),
                 {
-                    "message_id": message_id,
                     "content": "Hello @**everyone**",
                 },
             )
@@ -1234,7 +1209,6 @@ def test_wildcard_mention_restrictions_when_editing(self) -> None:
             result = self.client_patch(
                 "/json/messages/" + str(message_id),
                 {
-                    "message_id": message_id,
                     "content": "Hello @**everyone**",
                 },
             )
@@ -1269,7 +1243,6 @@ def verify_edit_history(new_topic: str, len_edit_history: int) -> None:
         result = self.client_patch(
             f"/json/messages/{id1}",
             {
-                "message_id": id1,
                 "topic": new_topic,
                 "propagate_mode": "change_later",
             },
@@ -1282,7 +1255,6 @@ def verify_edit_history(new_topic: str, len_edit_history: int) -> None:
         result = self.client_patch(
             f"/json/messages/{id1}",
             {
-                "message_id": id1,
                 "topic": new_topic,
                 "propagate_mode": "change_later",
             },
@@ -1301,7 +1273,6 @@ def test_topic_and_content_edit(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(id1),
             {
-                "message_id": id1,
                 "topic": new_topic,
                 "propagate_mode": "change_later",
                 "content": "edited message",
@@ -1340,7 +1311,6 @@ def test_propagate_topic_forward(self) -> None:
         result = self.client_patch(
             f"/json/messages/{id1}",
             {
-                "message_id": id1,
                 "topic": "edited",
                 "propagate_mode": "change_later",
             },
@@ -1365,7 +1335,6 @@ def test_propagate_all_topics(self) -> None:
         result = self.client_patch(
             f"/json/messages/{id2}",
             {
-                "message_id": id2,
                 "topic": "edited",
                 "propagate_mode": "change_all",
             },
@@ -1389,7 +1358,6 @@ def test_propagate_all_topics_with_different_uppercase_letters(self) -> None:
         result = self.client_patch(
             f"/json/messages/{id2}",
             {
-                "message_id": id2,
                 "topic": "edited",
                 "propagate_mode": "change_all",
             },
@@ -1415,7 +1383,6 @@ def test_move_message_to_stream(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
             },
@@ -1428,14 +1395,14 @@ def test_move_message_to_stream(self) -> None:
         self.assert_length(messages, 1)
         self.assertEqual(
             messages[0].content,
-            f"This topic was moved by @_**Iago|{user_profile.id}** to #**new stream>test**",
+            f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
         messages = get_topic_messages(user_profile, new_stream, "test")
         self.assert_length(messages, 4)
         self.assertEqual(
             messages[3].content,
-            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**",
+            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
     def test_move_message_realm_admin_cant_move_to_another_realm(self) -> None:
@@ -1451,7 +1418,6 @@ def test_move_message_realm_admin_cant_move_to_another_realm(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
             },
@@ -1472,7 +1438,6 @@ def test_move_message_realm_admin_cant_move_to_private_stream_without_subscripti
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
             },
@@ -1497,7 +1462,6 @@ def test_move_message_realm_admin_cant_move_from_private_stream_without_subscrip
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": verona.id,
                 "propagate_mode": "change_all",
             },
@@ -1541,7 +1505,6 @@ def test_move_message_from_private_stream_message_access_checks(
         result = self.client_patch(
             "/json/messages/" + str(new_msg_id),
             {
-                "message_id": new_msg_id,
                 "stream_id": verona.id,
                 "propagate_mode": "change_all",
             },
@@ -1568,7 +1531,6 @@ def test_move_message_to_stream_change_later(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id_later}",
             {
-                "message_id": msg_id_later,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_later",
             },
@@ -1580,7 +1542,7 @@ def test_move_message_to_stream_change_later(self) -> None:
         self.assertEqual(messages[0].id, msg_id)
         self.assertEqual(
             messages[1].content,
-            f"This topic was moved by @_**Iago|{user_profile.id}** to #**new stream>test**",
+            f"2 messages were moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
         messages = get_topic_messages(user_profile, new_stream, "test")
@@ -1588,7 +1550,95 @@ def test_move_message_to_stream_change_later(self) -> None:
         self.assertEqual(messages[0].id, msg_id_later)
         self.assertEqual(
             messages[2].content,
-            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**",
+            f"2 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+    def test_move_message_to_stream_change_later_all_moved(self) -> None:
+        (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
+            "iago", "test move stream", "new stream", "test"
+        )
+
+        result = self.client_patch(
+            f"/json/messages/{msg_id}",
+            {
+                "stream_id": new_stream.id,
+                "propagate_mode": "change_later",
+            },
+        )
+        self.assert_json_success(result)
+
+        messages = get_topic_messages(user_profile, old_stream, "test")
+        self.assert_length(messages, 1)
+        self.assertEqual(
+            messages[0].content,
+            f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+        messages = get_topic_messages(user_profile, new_stream, "test")
+        self.assert_length(messages, 4)
+        self.assertEqual(messages[0].id, msg_id)
+        self.assertEqual(
+            messages[3].content,
+            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+    def test_move_message_to_stream_change_one(self) -> None:
+        (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
+            "iago", "test move stream", "new stream", "test"
+        )
+
+        result = self.client_patch(
+            "/json/messages/" + str(msg_id_later),
+            {
+                "stream_id": new_stream.id,
+                "propagate_mode": "change_one",
+            },
+        )
+        self.assert_json_success(result)
+
+        messages = get_topic_messages(user_profile, old_stream, "test")
+        self.assert_length(messages, 3)
+        self.assertEqual(messages[0].id, msg_id)
+        self.assertEqual(
+            messages[2].content,
+            f"A message was moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+        messages = get_topic_messages(user_profile, new_stream, "test")
+        self.assert_length(messages, 2)
+        self.assertEqual(messages[0].id, msg_id_later)
+        self.assertEqual(
+            messages[1].content,
+            f"A message was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+    def test_move_message_to_stream_change_all(self) -> None:
+        (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
+            "iago", "test move stream", "new stream", "test"
+        )
+
+        result = self.client_patch(
+            "/json/messages/" + str(msg_id_later),
+            {
+                "stream_id": new_stream.id,
+                "propagate_mode": "change_all",
+            },
+        )
+        self.assert_json_success(result)
+
+        messages = get_topic_messages(user_profile, old_stream, "test")
+        self.assert_length(messages, 1)
+        self.assertEqual(
+            messages[0].content,
+            f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
+        )
+
+        messages = get_topic_messages(user_profile, new_stream, "test")
+        self.assert_length(messages, 4)
+        self.assertEqual(messages[0].id, msg_id)
+        self.assertEqual(
+            messages[3].content,
+            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
     def test_move_message_between_streams_policy_setting(self) -> None:
@@ -1602,7 +1652,6 @@ def check_move_message_according_to_policy(role: int, expect_fail: bool = False)
             result = self.client_patch(
                 "/json/messages/" + str(msg_id),
                 {
-                    "message_id": msg_id,
                     "stream_id": new_stream.id,
                     "propagate_mode": "change_all",
                 },
@@ -1692,7 +1741,6 @@ def check_move_message_to_stream(role: int, error_msg: Optional[str] = None) ->
             result = self.client_patch(
                 "/json/messages/" + str(msg_id),
                 {
-                    "message_id": msg_id,
                     "stream_id": new_stream.id,
                     "propagate_mode": "change_all",
                 },
@@ -1788,7 +1836,6 @@ def test_move_message_to_stream_with_topic_editing_not_allowed(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(msg_id),
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "topic": "new topic",
@@ -1799,7 +1846,6 @@ def test_move_message_to_stream_with_topic_editing_not_allowed(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
             },
@@ -1819,7 +1865,6 @@ def test_move_message_to_stream_and_topic(self) -> None:
             result = self.client_patch(
                 f"/json/messages/{msg_id}",
                 {
-                    "message_id": msg_id,
                     "stream_id": new_stream.id,
                     "propagate_mode": "change_all",
                     "topic": "new topic",
@@ -1832,14 +1877,14 @@ def test_move_message_to_stream_and_topic(self) -> None:
         self.assert_length(messages, 1)
         self.assertEqual(
             messages[0].content,
-            f"This topic was moved by @_**Iago|{user_profile.id}** to #**new stream>new topic**",
+            f"This topic was moved to #**new stream>new topic** by @_**Iago|{user_profile.id}**.",
         )
 
         messages = get_topic_messages(user_profile, new_stream, "new topic")
         self.assert_length(messages, 4)
         self.assertEqual(
             messages[3].content,
-            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**",
+            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
         )
         self.assert_json_success(result)
 
@@ -1885,7 +1930,6 @@ def test_inaccessible_msg_after_stream_change(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "topic": "new topic",
@@ -1965,7 +2009,6 @@ def test_no_notify_move_message_to_stream(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "send_notification_to_old_thread": "false",
@@ -1989,7 +2032,6 @@ def test_notify_new_thread_move_message_to_stream(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "send_notification_to_old_thread": "false",
@@ -2006,7 +2048,7 @@ def test_notify_new_thread_move_message_to_stream(self) -> None:
         self.assert_length(messages, 4)
         self.assertEqual(
             messages[3].content,
-            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**",
+            f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
     def test_notify_old_thread_move_message_to_stream(self) -> None:
@@ -2017,7 +2059,6 @@ def test_notify_old_thread_move_message_to_stream(self) -> None:
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
                 "send_notification_to_old_thread": "true",
@@ -2031,7 +2072,7 @@ def test_notify_old_thread_move_message_to_stream(self) -> None:
         self.assert_length(messages, 1)
         self.assertEqual(
             messages[0].content,
-            f"This topic was moved by @_**Iago|{user_profile.id}** to #**new stream>test**",
+            f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
         )
 
         messages = get_topic_messages(user_profile, new_stream, "test")
@@ -2085,7 +2126,6 @@ def parameterized_test_move_message_involving_private_stream(
         result = self.client_patch(
             f"/json/messages/{msg_id}",
             {
-                "message_id": msg_id,
                 "stream_id": new_stream.id,
                 "propagate_mode": "change_all",
             },
@@ -2096,7 +2136,7 @@ def parameterized_test_move_message_involving_private_stream(
         self.assert_length(messages, 1)
         self.assertEqual(
             messages[0].content,
-            f"This topic was moved by @_**Iago|{admin_user.id}** to #**new stream>test**",
+            f"This topic was moved to #**new stream>test** by @_**Iago|{admin_user.id}**.",
         )
 
         messages = get_topic_messages(admin_user, new_stream, "test")
@@ -2191,7 +2231,6 @@ def test_mark_topic_as_resolved(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(id1),
             {
-                "message_id": id1,
                 "topic": original_topic,
                 "propagate_mode": "change_all",
             },
@@ -2202,7 +2241,6 @@ def test_mark_topic_as_resolved(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(id1),
             {
-                "message_id": id1,
                 "topic": resolved_topic,
                 "propagate_mode": "change_all",
             },
@@ -2246,7 +2284,6 @@ def test_mark_topic_as_resolved(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(id1),
             {
-                "message_id": id1,
                 "topic": weird_topic,
                 "propagate_mode": "change_all",
             },
@@ -2271,7 +2308,6 @@ def test_mark_topic_as_resolved(self) -> None:
         result = self.client_patch(
             "/json/messages/" + str(id1),
             {
-                "message_id": id1,
                 "topic": unresolved_topic,
                 "propagate_mode": "change_all",
             },