From 3dc7d60ffecd8d521e2bba0cc6e87c6e9bd3c213 Mon Sep 17 00:00:00 2001 From: Ryan Rehman Date: Fri, 17 Jan 2020 20:31:00 +0530 Subject: [PATCH] muting: Record DateTime when a Topic is muted. This includes the necessary migration to add the date_muted field to the MutedTopic class and populates it with a hard coded value. --- zerver/lib/actions.py | 7 +++++-- zerver/lib/export.py | 1 + zerver/lib/import_realm.py | 1 + zerver/lib/topic_mutes.py | 15 +++++++++++-- .../migrations/0262_mutedtopic_date_muted.py | 21 +++++++++++++++++++ zerver/models.py | 9 +++++++- zerver/tests/test_muting.py | 10 +++++++++ zerver/views/muting.py | 8 +++++-- 8 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 zerver/migrations/0262_mutedtopic_date_muted.py diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 3621330815..ea32c15cf5 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -5286,8 +5286,11 @@ def do_set_alert_words(user_profile: UserProfile, alert_words: List[str]) -> Non set_user_alert_words(user_profile, alert_words) notify_alert_words(user_profile, alert_words) -def do_mute_topic(user_profile: UserProfile, stream: Stream, recipient: Recipient, topic: str) -> None: - add_topic_mute(user_profile, stream.id, recipient.id, topic) +def do_mute_topic(user_profile: UserProfile, stream: Stream, recipient: Recipient, topic: str, + date_muted: Optional[datetime.datetime]=None) -> None: + if date_muted is None: + date_muted = timezone_now() + add_topic_mute(user_profile, stream.id, recipient.id, topic, date_muted) event = dict(type="muted_topics", muted_topics=get_topic_mutes(user_profile)) send_event(user_profile.realm, event, [user_profile.id]) diff --git a/zerver/lib/export.py b/zerver/lib/export.py index 8a2d49db8f..162950e77e 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -247,6 +247,7 @@ ANALYTICS_TABLES = { DATE_FIELDS = { 'zerver_attachment': ['create_time'], 'zerver_message': ['last_edit_time', 'date_sent'], + 'zerver_mutedtopic': ['date_muted'], 'zerver_realm': ['date_created'], 'zerver_stream': ['date_created'], 'zerver_useractivity': ['last_visit'], diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index 4d10e9e2df..a358d36323 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -914,6 +914,7 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int=1) -> Realm bulk_import_model(data, UserHotspot) if 'zerver_mutedtopic' in data: + fix_datetime_fields(data, 'zerver_mutedtopic') re_map_foreign_keys(data, 'zerver_mutedtopic', 'user_profile', related_table='user_profile') re_map_foreign_keys(data, 'zerver_mutedtopic', 'stream', related_table='stream') re_map_foreign_keys(data, 'zerver_mutedtopic', 'recipient', related_table='recipient') diff --git a/zerver/lib/topic_mutes.py b/zerver/lib/topic_mutes.py index 6e308d4de8..946d9aed1b 100644 --- a/zerver/lib/topic_mutes.py +++ b/zerver/lib/topic_mutes.py @@ -1,4 +1,5 @@ from typing import Any, Callable, Dict, List, Optional +import datetime from zerver.lib.topic import ( topic_match_sa, @@ -17,6 +18,8 @@ from sqlalchemy.sql import ( Selectable ) +from django.utils.timezone import now as timezone_now + def get_topic_mutes(user_profile: UserProfile) -> List[List[str]]: rows = MutedTopic.objects.filter( user_profile=user_profile, @@ -29,7 +32,8 @@ def get_topic_mutes(user_profile: UserProfile) -> List[List[str]]: for row in rows ] -def set_topic_mutes(user_profile: UserProfile, muted_topics: List[List[str]]) -> None: +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. @@ -39,6 +43,8 @@ def set_topic_mutes(user_profile: UserProfile, muted_topics: List[List[str]]) -> user_profile=user_profile, ).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 = get_stream_recipient(stream.id) @@ -48,14 +54,19 @@ def set_topic_mutes(user_profile: UserProfile, muted_topics: List[List[str]]) -> 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) -> None: +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() MutedTopic.objects.create( user_profile=user_profile, stream_id=stream_id, recipient_id=recipient_id, topic_name=topic_name, + date_muted=date_muted, ) def remove_topic_mute(user_profile: UserProfile, stream_id: int, topic_name: str) -> None: diff --git a/zerver/migrations/0262_mutedtopic_date_muted.py b/zerver/migrations/0262_mutedtopic_date_muted.py new file mode 100644 index 0000000000..9f34e24d9c --- /dev/null +++ b/zerver/migrations/0262_mutedtopic_date_muted.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.26 on 2020-01-17 15:26 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0261_realm_private_message_policy'), + ] + + operations = [ + migrations.AddField( + model_name='mutedtopic', + name='date_muted', + field=models.DateTimeField(default=datetime.datetime(2020, 1, 1, 0, 0)), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 0634dc7ff0..755579b4d9 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1457,12 +1457,19 @@ class MutedTopic(models.Model): stream = models.ForeignKey(Stream, on_delete=CASCADE) recipient = models.ForeignKey(Recipient, on_delete=CASCADE) topic_name = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH) + # The default value for date_muted is a few weeks before tracking + # of when topics were muted was first introduced. It's designed + # to be obviously incorrect so that users can tell it's backfilled data. + date_muted = models.DateTimeField(default=datetime.datetime(2020, 1, 1, 0, 0, 0, 0)) class Meta: unique_together = ('user_profile', 'stream', 'topic_name') def __str__(self) -> str: - return "" % (self.user_profile.email, self.stream.name, self.topic_name) + return ("" % (self.user_profile.email, + self.stream.name, + self.topic_name, + self.date_muted)) class Client(models.Model): name = models.CharField(max_length=30, db_index=True, unique=True) # type: str diff --git a/zerver/tests/test_muting.py b/zerver/tests/test_muting.py index f6f07df637..bf17f88670 100644 --- a/zerver/tests/test_muting.py +++ b/zerver/tests/test_muting.py @@ -1,3 +1,5 @@ +from django.utils.timezone import now as timezone_now +from datetime import timedelta from typing import Any, Dict from zerver.lib.test_classes import ZulipTestCase @@ -7,6 +9,7 @@ from zerver.models import ( get_stream, get_stream_recipient, UserProfile, + MutedTopic ) from zerver.lib.topic_mutes import ( @@ -39,15 +42,20 @@ class MutedTopicsTests(ZulipTestCase): stream_id=stream.id, recipient_id=recipient.id, topic_name='test TOPIC', + date_muted=timezone_now(), ) mute_user(hamlet) user_ids = stream_topic_target.user_ids_muting_topic() self.assertEqual(user_ids, {hamlet.id}) + hamlet_date_muted = MutedTopic.objects.filter(user_profile=hamlet)[0].date_muted + self.assertTrue(timezone_now() - hamlet_date_muted <= timedelta(seconds=100)) mute_user(cordelia) user_ids = stream_topic_target.user_ids_muting_topic() self.assertEqual(user_ids, {hamlet.id, cordelia.id}) + cordelia_date_muted = MutedTopic.objects.filter(user_profile=cordelia)[0].date_muted + self.assertTrue(timezone_now() - cordelia_date_muted <= timedelta(seconds=100)) def test_add_muted_topic(self) -> None: user = self.example_user('hamlet') @@ -98,6 +106,7 @@ class MutedTopicsTests(ZulipTestCase): stream_id=stream.id, recipient_id=recipient.id, topic_name='Verona3', + date_muted=timezone_now(), ) self.assertIn([stream.name, 'Verona3'], get_topic_mutes(user)) @@ -120,6 +129,7 @@ class MutedTopicsTests(ZulipTestCase): stream_id=stream.id, recipient_id=recipient.id, topic_name=u'Verona3', + date_muted=timezone_now(), ) url = '/api/v1/users/me/subscriptions/muted_topics' diff --git a/zerver/views/muting.py b/zerver/views/muting.py index 758866b60e..77dbfdcd75 100644 --- a/zerver/views/muting.py +++ b/zerver/views/muting.py @@ -1,6 +1,8 @@ from django.http import HttpResponse, HttpRequest from typing import Optional +import datetime +from django.utils.timezone import now as timezone_now from django.utils.translation import ugettext as _ from zerver.lib.actions import do_mute_topic, do_unmute_topic from zerver.lib.request import has_request_variables, REQ @@ -19,7 +21,8 @@ from zerver.models import UserProfile def mute_topic(user_profile: UserProfile, stream_id: Optional[int], stream_name: Optional[str], - topic_name: str) -> HttpResponse: + topic_name: str, + date_muted: datetime.datetime) -> HttpResponse: if stream_name is not None: (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name) else: @@ -29,7 +32,7 @@ def mute_topic(user_profile: UserProfile, if topic_is_muted(user_profile, stream.id, topic_name): return json_error(_("Topic already muted")) - do_mute_topic(user_profile, stream, recipient, topic_name) + do_mute_topic(user_profile, stream, recipient, topic_name, date_muted) return json_success() def unmute_topic(user_profile: UserProfile, @@ -66,6 +69,7 @@ def update_muted_topic(request: HttpRequest, stream_id=stream_id, stream_name=stream, topic_name=topic, + date_muted=timezone_now(), ) elif op == 'remove': return unmute_topic(