mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
Previously, when a topic was edited (including being resolved), it would become unmuted for any users who had muted it, which was annoying. While it's not possible to determine the user's intent completely, this is clearly incorrect behavior in the `change_all` case, such as resolving a topic. The comments discuss some scenarios where we might want to enhance this further, but this is the best we can do without large increases in complexity. Fixes #15210. Co-authored-by: akshatdalton <akshat.dak@students.iiit.ac.in>
166 lines
4.9 KiB
Python
166 lines
4.9 KiB
Python
import datetime
|
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
|
|
from django.db.models.query import QuerySet
|
|
from django.utils.timezone import now as timezone_now
|
|
from sqlalchemy.sql import ClauseElement, and_, column, not_, or_
|
|
from sqlalchemy.types import Integer
|
|
|
|
from zerver.lib.timestamp import datetime_to_timestamp
|
|
from zerver.lib.topic import topic_match_sa
|
|
from zerver.models import UserProfile, UserTopic, get_stream
|
|
|
|
|
|
def get_topic_mutes(
|
|
user_profile: UserProfile, include_deactivated: bool = False
|
|
) -> List[Tuple[str, str, float]]:
|
|
query = UserTopic.objects.filter(user_profile=user_profile, visibility_policy=UserTopic.MUTED)
|
|
# Exclude muted topics that are part of deactivated streams unless
|
|
# explicitly requested.
|
|
if not include_deactivated:
|
|
query = query.filter(stream__deactivated=False)
|
|
rows = query.values(
|
|
"stream__name",
|
|
"topic_name",
|
|
"last_updated",
|
|
)
|
|
return [
|
|
(row["stream__name"], row["topic_name"], datetime_to_timestamp(row["last_updated"]))
|
|
for row in rows
|
|
]
|
|
|
|
|
|
def set_topic_mutes(
|
|
user_profile: UserProfile,
|
|
muted_topics: List[List[str]],
|
|
date_muted: Optional[datetime.datetime] = None,
|
|
) -> None:
|
|
"""
|
|
This is only used in tests.
|
|
"""
|
|
|
|
UserTopic.objects.filter(
|
|
user_profile=user_profile,
|
|
visibility_policy=UserTopic.MUTED,
|
|
).delete()
|
|
|
|
if date_muted is None:
|
|
date_muted = timezone_now()
|
|
for stream_name, topic_name in muted_topics:
|
|
stream = get_stream(stream_name, user_profile.realm)
|
|
recipient_id = stream.recipient_id
|
|
|
|
add_topic_mute(
|
|
user_profile=user_profile,
|
|
stream_id=stream.id,
|
|
recipient_id=recipient_id,
|
|
topic_name=topic_name,
|
|
date_muted=date_muted,
|
|
)
|
|
|
|
|
|
def add_topic_mute(
|
|
user_profile: UserProfile,
|
|
stream_id: int,
|
|
recipient_id: int,
|
|
topic_name: str,
|
|
date_muted: Optional[datetime.datetime] = None,
|
|
) -> None:
|
|
if date_muted is None:
|
|
date_muted = timezone_now()
|
|
UserTopic.objects.create(
|
|
user_profile=user_profile,
|
|
stream_id=stream_id,
|
|
recipient_id=recipient_id,
|
|
topic_name=topic_name,
|
|
last_updated=date_muted,
|
|
visibility_policy=UserTopic.MUTED,
|
|
)
|
|
|
|
|
|
def remove_topic_mute(user_profile: UserProfile, stream_id: int, topic_name: str) -> None:
|
|
row = UserTopic.objects.get(
|
|
user_profile=user_profile,
|
|
stream_id=stream_id,
|
|
topic_name__iexact=topic_name,
|
|
visibility_policy=UserTopic.MUTED,
|
|
)
|
|
row.delete()
|
|
|
|
|
|
def topic_is_muted(user_profile: UserProfile, stream_id: int, topic_name: str) -> bool:
|
|
is_muted = UserTopic.objects.filter(
|
|
user_profile=user_profile,
|
|
stream_id=stream_id,
|
|
topic_name__iexact=topic_name,
|
|
visibility_policy=UserTopic.MUTED,
|
|
).exists()
|
|
return is_muted
|
|
|
|
|
|
def exclude_topic_mutes(
|
|
conditions: List[ClauseElement], user_profile: UserProfile, stream_id: Optional[int]
|
|
) -> List[ClauseElement]:
|
|
# Note: Unlike get_topic_mutes, here we always want to
|
|
# consider topics in deactivated streams, so they are
|
|
# never filtered from the query in this method.
|
|
query = UserTopic.objects.filter(
|
|
user_profile=user_profile,
|
|
visibility_policy=UserTopic.MUTED,
|
|
)
|
|
|
|
if stream_id is not None:
|
|
# If we are narrowed to a stream, we can optimize the query
|
|
# by not considering topic mutes outside the stream.
|
|
query = query.filter(stream_id=stream_id)
|
|
|
|
query = query.values(
|
|
"recipient_id",
|
|
"topic_name",
|
|
)
|
|
rows = list(query)
|
|
|
|
if not rows:
|
|
return conditions
|
|
|
|
def mute_cond(row: Dict[str, Any]) -> ClauseElement:
|
|
recipient_id = row["recipient_id"]
|
|
topic_name = row["topic_name"]
|
|
stream_cond = column("recipient_id", Integer) == recipient_id
|
|
topic_cond = topic_match_sa(topic_name)
|
|
return and_(stream_cond, topic_cond)
|
|
|
|
condition = not_(or_(*list(map(mute_cond, rows))))
|
|
return [*conditions, condition]
|
|
|
|
|
|
def build_topic_mute_checker(user_profile: UserProfile) -> Callable[[int, str], bool]:
|
|
rows = UserTopic.objects.filter(
|
|
user_profile=user_profile, visibility_policy=UserTopic.MUTED
|
|
).values(
|
|
"recipient_id",
|
|
"topic_name",
|
|
)
|
|
rows = list(rows)
|
|
|
|
tups = set()
|
|
for row in rows:
|
|
recipient_id = row["recipient_id"]
|
|
topic_name = row["topic_name"]
|
|
tups.add((recipient_id, topic_name.lower()))
|
|
|
|
def is_muted(recipient_id: int, topic: str) -> bool:
|
|
return (recipient_id, topic.lower()) in tups
|
|
|
|
return is_muted
|
|
|
|
|
|
def get_users_muting_topic(stream_id: int, topic_name: str) -> QuerySet[UserProfile]:
|
|
return UserProfile.objects.select_related("realm").filter(
|
|
id__in=UserTopic.objects.filter(
|
|
stream_id=stream_id,
|
|
visibility_policy=UserTopic.MUTED,
|
|
topic_name__iexact=topic_name,
|
|
).values("user_profile_id")
|
|
)
|