mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	user_topics: Update UserTopic records regardless of the visibility_policy.
This commit updates the 'do_update_message' codepath to update the UserTopic records regardless of visibility policy during the "move-topic" operation. This is required before offering new visibility policies in the UI. Previously, UserTopic records were moved or deleted only for objects with a MUTED visibility policy. Fixes: #24574
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							0377085f15
						
					
				
				
					commit
					a890aaf34d
				
			@@ -50,7 +50,7 @@ from zerver.lib.topic import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from zerver.lib.types import EditHistoryEvent
 | 
					from zerver.lib.types import EditHistoryEvent
 | 
				
			||||||
from zerver.lib.user_message import UserMessageLite, bulk_insert_ums
 | 
					from zerver.lib.user_message import UserMessageLite, bulk_insert_ums
 | 
				
			||||||
from zerver.lib.user_topics import get_users_muting_topic
 | 
					from zerver.lib.user_topics import get_users_with_user_topic_visibility_policy
 | 
				
			||||||
from zerver.lib.widget import is_widget_message
 | 
					from zerver.lib.widget import is_widget_message
 | 
				
			||||||
from zerver.models import (
 | 
					from zerver.models import (
 | 
				
			||||||
    ArchivedAttachment,
 | 
					    ArchivedAttachment,
 | 
				
			||||||
@@ -749,7 +749,8 @@ def do_update_message(
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            moved_all_visible_messages = len(visible_unmoved_messages) == 0
 | 
					            moved_all_visible_messages = len(visible_unmoved_messages) == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Migrate muted topic configuration in the following circumstances:
 | 
					    # Migrate 'topic with visibility_policy' configuration in the following
 | 
				
			||||||
 | 
					    # circumstances:
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # * If propagate_mode is change_all, do so unconditionally.
 | 
					    # * If propagate_mode is change_all, do so unconditionally.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
@@ -765,50 +766,56 @@ def do_update_message(
 | 
				
			|||||||
        assert stream_being_edited is not None
 | 
					        assert stream_being_edited is not None
 | 
				
			||||||
        assert topic_name is not None or new_stream is not None
 | 
					        assert topic_name is not None or new_stream is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for muting_user in get_users_muting_topic(stream_being_edited.id, orig_topic_name):
 | 
					        for user_topic in get_users_with_user_topic_visibility_policy(
 | 
				
			||||||
 | 
					            stream_being_edited.id, orig_topic_name
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
            # TODO: Ideally, this would be a bulk update operation,
 | 
					            # TODO: Ideally, this would be a bulk update operation,
 | 
				
			||||||
            # because we are doing database operations in a loop here.
 | 
					            # because we are doing database operations in a loop here.
 | 
				
			||||||
            #
 | 
					            #
 | 
				
			||||||
            # This loop is only acceptable in production because it is
 | 
					            # This loop is only acceptable in production because it is
 | 
				
			||||||
            # rare for more than a few users to have muted an
 | 
					            # rare for more than a few users to have visibility_policy
 | 
				
			||||||
            # individual topic that is being moved; as of this
 | 
					            # set for an individual topic that is being moved; as of this
 | 
				
			||||||
            # writing, no individual topic in Zulip Cloud had been
 | 
					            # writing, no individual topic in Zulip Cloud had been
 | 
				
			||||||
            # muted by more than 100 users.
 | 
					            # muted by more than 100 users.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if new_stream is not None and muting_user.id in delete_event_notify_user_ids:
 | 
					            if (
 | 
				
			||||||
 | 
					                new_stream is not None
 | 
				
			||||||
 | 
					                and user_topic.user_profile_id in delete_event_notify_user_ids
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
                # If the messages are being moved to a stream the user
 | 
					                # If the messages are being moved to a stream the user
 | 
				
			||||||
                # cannot access, then we treat this as the
 | 
					                # cannot access, then we treat this as the
 | 
				
			||||||
                # messages/topic being deleted for this user. This is
 | 
					                # messages/topic being deleted for this user. This is
 | 
				
			||||||
                # important for security reasons; we don't want to
 | 
					                # important for security reasons; we don't want to
 | 
				
			||||||
                # give users a UserTopic row in a stream they cannot
 | 
					                # give users a UserTopic row in a stream they cannot
 | 
				
			||||||
                # access.  Unmute the topic for such users.
 | 
					                # access. Remove the user topic rows for such users.
 | 
				
			||||||
                do_set_user_topic_visibility_policy(
 | 
					                do_set_user_topic_visibility_policy(
 | 
				
			||||||
                    muting_user,
 | 
					                    user_topic.user_profile,
 | 
				
			||||||
                    stream_being_edited,
 | 
					                    stream_being_edited,
 | 
				
			||||||
                    orig_topic_name,
 | 
					                    orig_topic_name,
 | 
				
			||||||
                    visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | 
					                    visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # Otherwise, we move the muted topic record for the
 | 
					                # Otherwise, we move the user topic record for the
 | 
				
			||||||
                # user, but removing the old topic mute and then
 | 
					                # user, but removing the old topic visibility_policy
 | 
				
			||||||
                # creating a new one.
 | 
					                # and then creating a new one.
 | 
				
			||||||
 | 
					                new_visibility_policy = user_topic.visibility_policy
 | 
				
			||||||
                do_set_user_topic_visibility_policy(
 | 
					                do_set_user_topic_visibility_policy(
 | 
				
			||||||
                    muting_user,
 | 
					                    user_topic.user_profile,
 | 
				
			||||||
                    stream_being_edited,
 | 
					                    stream_being_edited,
 | 
				
			||||||
                    orig_topic_name,
 | 
					                    orig_topic_name,
 | 
				
			||||||
                    visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | 
					                    visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | 
				
			||||||
                    # do_set_user_topic_visibility_policy with visibility_policy
 | 
					                    # do_set_user_topic_visibility_policy with visibility_policy
 | 
				
			||||||
                    # set to UserTopic.VisibilityPolicy.MUTED will send an updated muted topic
 | 
					                    # set to 'new_visibility_policy' will send an updated muted topic
 | 
				
			||||||
                    # event, which contains the full set of muted
 | 
					                    # event, which contains the full set of muted
 | 
				
			||||||
                    # topics, just after this.
 | 
					                    # topics, just after this.
 | 
				
			||||||
                    skip_muted_topics_event=True,
 | 
					                    skip_muted_topics_event=True,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                do_set_user_topic_visibility_policy(
 | 
					                do_set_user_topic_visibility_policy(
 | 
				
			||||||
                    muting_user,
 | 
					                    user_topic.user_profile,
 | 
				
			||||||
                    new_stream if new_stream is not None else stream_being_edited,
 | 
					                    new_stream if new_stream is not None else stream_being_edited,
 | 
				
			||||||
                    topic_name if topic_name is not None else orig_topic_name,
 | 
					                    topic_name if topic_name is not None else orig_topic_name,
 | 
				
			||||||
                    visibility_policy=UserTopic.VisibilityPolicy.MUTED,
 | 
					                    visibility_policy=new_visibility_policy,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    send_event(user_profile.realm, event, users_to_be_notified)
 | 
					    send_event(user_profile.realm, event, users_to_be_notified)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -239,11 +239,9 @@ def build_topic_mute_checker(user_profile: UserProfile) -> Callable[[int, str],
 | 
				
			|||||||
    return is_muted
 | 
					    return is_muted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_users_muting_topic(stream_id: int, topic_name: str) -> QuerySet[UserProfile]:
 | 
					def get_users_with_user_topic_visibility_policy(
 | 
				
			||||||
    return UserProfile.objects.select_related("realm").filter(
 | 
					    stream_id: int, topic_name: str
 | 
				
			||||||
        id__in=UserTopic.objects.filter(
 | 
					) -> QuerySet[UserTopic]:
 | 
				
			||||||
            stream_id=stream_id,
 | 
					    return UserTopic.objects.filter(
 | 
				
			||||||
            visibility_policy=UserTopic.VisibilityPolicy.MUTED,
 | 
					        stream_id=stream_id, topic_name__iexact=topic_name
 | 
				
			||||||
            topic_name__iexact=topic_name,
 | 
					    ).select_related("user_profile", "user_profile__realm")
 | 
				
			||||||
        ).values("user_profile_id")
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ from zerver.lib.test_helpers import cache_tries_captured, queries_captured
 | 
				
			|||||||
from zerver.lib.topic import RESOLVED_TOPIC_PREFIX, TOPIC_NAME
 | 
					from zerver.lib.topic import RESOLVED_TOPIC_PREFIX, TOPIC_NAME
 | 
				
			||||||
from zerver.lib.user_topics import (
 | 
					from zerver.lib.user_topics import (
 | 
				
			||||||
    get_topic_mutes,
 | 
					    get_topic_mutes,
 | 
				
			||||||
    get_users_muting_topic,
 | 
					    get_users_with_user_topic_visibility_policy,
 | 
				
			||||||
    set_topic_visibility_policy,
 | 
					    set_topic_visibility_policy,
 | 
				
			||||||
    topic_has_visibility_policy,
 | 
					    topic_has_visibility_policy,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -1371,10 +1371,12 @@ class EditMessageTest(EditMessageTestCase):
 | 
				
			|||||||
                content=None,
 | 
					                content=None,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for muting_user in get_users_muting_topic(stream.id, change_all_topic_name):
 | 
					        for user_topic in get_users_with_user_topic_visibility_policy(
 | 
				
			||||||
 | 
					            stream.id, change_all_topic_name
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
            for user in users_to_be_notified:
 | 
					            for user in users_to_be_notified:
 | 
				
			||||||
                if muting_user.id == user["id"]:
 | 
					                if user_topic.user_profile_id == user["id"]:
 | 
				
			||||||
                    user["muted_topics"] = get_topic_mutes(muting_user)
 | 
					                    user["muted_topics"] = get_topic_mutes(user_topic.user_profile)
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_is_topic_muted(hamlet, stream.id, "Topic1", muted=False)
 | 
					        assert_is_topic_muted(hamlet, stream.id, "Topic1", muted=False)
 | 
				
			||||||
@@ -1534,7 +1536,7 @@ class EditMessageTest(EditMessageTestCase):
 | 
				
			|||||||
        assert_is_topic_muted(cordelia, new_public_stream.id, "changed topic name", muted=True)
 | 
					        assert_is_topic_muted(cordelia, new_public_stream.id, "changed topic name", muted=True)
 | 
				
			||||||
        assert_is_topic_muted(aaron, new_public_stream.id, "changed topic name", muted=False)
 | 
					        assert_is_topic_muted(aaron, new_public_stream.id, "changed topic name", muted=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Moving only half the messages doesn't move MutedTopic records.
 | 
					        # Moving only half the messages doesn't move UserTopic records.
 | 
				
			||||||
        second_message_id = self.send_stream_message(
 | 
					        second_message_id = self.send_stream_message(
 | 
				
			||||||
            hamlet, stream_name, topic_name="changed topic name", content="Second message"
 | 
					            hamlet, stream_name, topic_name="changed topic name", content="Second message"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -1557,6 +1559,136 @@ class EditMessageTest(EditMessageTestCase):
 | 
				
			|||||||
        assert_is_topic_muted(cordelia, new_public_stream.id, "final topic name", muted=False)
 | 
					        assert_is_topic_muted(cordelia, new_public_stream.id, "final topic name", muted=False)
 | 
				
			||||||
        assert_is_topic_muted(aaron, new_public_stream.id, "final topic name", muted=False)
 | 
					        assert_is_topic_muted(aaron, new_public_stream.id, "final topic name", muted=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @mock.patch("zerver.actions.user_topics.send_event")
 | 
				
			||||||
 | 
					    def test_edit_unmuted_topic(self, mock_send_event: mock.MagicMock) -> None:
 | 
				
			||||||
 | 
					        stream_name = "Stream 123"
 | 
				
			||||||
 | 
					        stream = self.make_stream(stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hamlet = self.example_user("hamlet")
 | 
				
			||||||
 | 
					        cordelia = self.example_user("cordelia")
 | 
				
			||||||
 | 
					        aaron = self.example_user("aaron")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            user_profile: UserProfile,
 | 
				
			||||||
 | 
					            topic_name: str,
 | 
				
			||||||
 | 
					            visibility_policy: int,
 | 
				
			||||||
 | 
					            *,
 | 
				
			||||||
 | 
					            expected: bool,
 | 
				
			||||||
 | 
					        ) -> None:
 | 
				
			||||||
 | 
					            if expected:
 | 
				
			||||||
 | 
					                self.assertTrue(
 | 
				
			||||||
 | 
					                    topic_has_visibility_policy(
 | 
				
			||||||
 | 
					                        user_profile, stream.id, topic_name, visibility_policy
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.assertFalse(
 | 
				
			||||||
 | 
					                    topic_has_visibility_policy(
 | 
				
			||||||
 | 
					                        user_profile, stream.id, topic_name, visibility_policy
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.subscribe(hamlet, stream_name)
 | 
				
			||||||
 | 
					        self.login_user(hamlet)
 | 
				
			||||||
 | 
					        message_id = self.send_stream_message(
 | 
				
			||||||
 | 
					            hamlet, stream_name, topic_name="Topic1", content="Hello World"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.subscribe(cordelia, stream_name)
 | 
				
			||||||
 | 
					        self.login_user(cordelia)
 | 
				
			||||||
 | 
					        self.subscribe(aaron, stream_name)
 | 
				
			||||||
 | 
					        self.login_user(aaron)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initially, hamlet sets visibility_policy as UNMUTED for 'Topic1' and 'Topic2',
 | 
				
			||||||
 | 
					        # cordelia sets visibility_policy as MUTED for 'Topic1' and 'Topic2', while
 | 
				
			||||||
 | 
					        # aaron doesn't have a visibility_policy set for 'Topic1' or 'Topic2'.
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # After moving messages from 'Topic1' to 'Topic 1 edited', the expected behaviour is:
 | 
				
			||||||
 | 
					        # hamlet has UNMUTED 'Topic 1 edited' and no visibility_policy set for 'Topic1'
 | 
				
			||||||
 | 
					        # cordelia has MUTED 'Topic 1 edited' and no visibility_policy set for 'Topic1'
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # There is no change in visibility_policy configurations for 'Topic2', i.e.
 | 
				
			||||||
 | 
					        # hamlet has UNMUTED 'Topic2' + cordelia has MUTED 'Topic2'
 | 
				
			||||||
 | 
					        # aaron still doesn't have visibility_policy set for any topic.
 | 
				
			||||||
 | 
					        topics = [
 | 
				
			||||||
 | 
					            [stream_name, "Topic1"],
 | 
				
			||||||
 | 
					            [stream_name, "Topic2"],
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        set_topic_visibility_policy(hamlet, topics, UserTopic.VisibilityPolicy.UNMUTED)
 | 
				
			||||||
 | 
					        set_topic_visibility_policy(cordelia, topics, UserTopic.VisibilityPolicy.MUTED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # users that need to be notified by send_event in the case of change-topic-name operation.
 | 
				
			||||||
 | 
					        users_to_be_notified_via_muted_topics_event: List[int] = []
 | 
				
			||||||
 | 
					        users_to_be_notified_via_user_topic_event: List[int] = []
 | 
				
			||||||
 | 
					        for user_topic in get_users_with_user_topic_visibility_policy(stream.id, "Topic1"):
 | 
				
			||||||
 | 
					            # We are appending the same data twice because 'user_topic' event notifies
 | 
				
			||||||
 | 
					            # the user during delete and create operation.
 | 
				
			||||||
 | 
					            users_to_be_notified_via_user_topic_event.append(user_topic.user_profile_id)
 | 
				
			||||||
 | 
					            users_to_be_notified_via_user_topic_event.append(user_topic.user_profile_id)
 | 
				
			||||||
 | 
					            # 'muted_topics' event notifies the user of muted topics during create
 | 
				
			||||||
 | 
					            # operation only.
 | 
				
			||||||
 | 
					            users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        change_all_topic_name = "Topic 1 edited"
 | 
				
			||||||
 | 
					        with self.assert_database_query_count(19):
 | 
				
			||||||
 | 
					            check_update_message(
 | 
				
			||||||
 | 
					                user_profile=hamlet,
 | 
				
			||||||
 | 
					                message_id=message_id,
 | 
				
			||||||
 | 
					                stream_id=None,
 | 
				
			||||||
 | 
					                topic_name=change_all_topic_name,
 | 
				
			||||||
 | 
					                propagate_mode="change_all",
 | 
				
			||||||
 | 
					                send_notification_to_old_thread=False,
 | 
				
			||||||
 | 
					                send_notification_to_new_thread=False,
 | 
				
			||||||
 | 
					                content=None,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Extract the send_event call where event type is 'user_topic' or 'muted_topics.
 | 
				
			||||||
 | 
					        # Here we assert that the expected users are notified properly.
 | 
				
			||||||
 | 
					        users_notified_via_muted_topics_event: List[int] = []
 | 
				
			||||||
 | 
					        users_notified_via_user_topic_event: List[int] = []
 | 
				
			||||||
 | 
					        for call_args in mock_send_event.call_args_list:
 | 
				
			||||||
 | 
					            (arg_realm, arg_event, arg_notified_users) = call_args[0]
 | 
				
			||||||
 | 
					            if arg_event["type"] == "user_topic":
 | 
				
			||||||
 | 
					                users_notified_via_user_topic_event.append(*arg_notified_users)
 | 
				
			||||||
 | 
					            elif arg_event["type"] == "muted_topics":
 | 
				
			||||||
 | 
					                users_notified_via_muted_topics_event.append(*arg_notified_users)
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            sorted(users_notified_via_muted_topics_event),
 | 
				
			||||||
 | 
					            sorted(users_to_be_notified_via_muted_topics_event),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            sorted(users_notified_via_user_topic_event),
 | 
				
			||||||
 | 
					            sorted(users_to_be_notified_via_user_topic_event),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            hamlet, "Topic1", UserTopic.VisibilityPolicy.UNMUTED, expected=False
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            cordelia, "Topic1", UserTopic.VisibilityPolicy.MUTED, expected=False
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            aaron, "Topic1", UserTopic.VisibilityPolicy.UNMUTED, expected=False
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            hamlet, "Topic2", UserTopic.VisibilityPolicy.UNMUTED, expected=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            cordelia, "Topic2", UserTopic.VisibilityPolicy.MUTED, expected=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            aaron, "Topic2", UserTopic.VisibilityPolicy.UNMUTED, expected=False
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            hamlet, change_all_topic_name, UserTopic.VisibilityPolicy.UNMUTED, expected=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            cordelia, change_all_topic_name, UserTopic.VisibilityPolicy.MUTED, expected=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert_has_visibility_policy(
 | 
				
			||||||
 | 
					            aaron, change_all_topic_name, UserTopic.VisibilityPolicy.MUTED, expected=False
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @mock.patch("zerver.actions.message_edit.send_event")
 | 
					    @mock.patch("zerver.actions.message_edit.send_event")
 | 
				
			||||||
    def test_wildcard_mention(self, mock_send_event: mock.MagicMock) -> None:
 | 
					    def test_wildcard_mention(self, mock_send_event: mock.MagicMock) -> None:
 | 
				
			||||||
        stream_name = "Macbeth"
 | 
					        stream_name = "Macbeth"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user