mirror of https://github.com/zulip/zulip.git
user_topic: Add user_topic event.
We now send a new user_topic event while muting and unmuting topics. fetch_initial_state_data now returns an additional user_topics array to the client that will maintain the user-topic relationship data. This will support any future addition of new features to modify the relationship between a user-topic pair. This commit adds the relevent backend code and schema for the new event.
This commit is contained in:
parent
8b674ee3d7
commit
1291e7000b
|
@ -20,6 +20,18 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 6.0
|
## Changes in Zulip 6.0
|
||||||
|
|
||||||
|
**Feature level 134**
|
||||||
|
|
||||||
|
* [`GET /events`](/api/get-events): Added `user_topic` event type
|
||||||
|
which is sent when a topic is muted or unmuted. This generalizes and
|
||||||
|
replaces the previous `muted_topics` array, which will no longer be
|
||||||
|
sent if `user_topic` was included in `event_types` when registering
|
||||||
|
the queue.
|
||||||
|
* [`POST /register`](/api/register-queue): Added `user_topics` array
|
||||||
|
to the response. This generalizes and replaces the previous
|
||||||
|
`muted_topics` array, which will no longer be sent if `user_topic`
|
||||||
|
is included in `fetch_event_types`.
|
||||||
|
|
||||||
**Feature level 133**
|
**Feature level 133**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue), `PATCH /realm`: Removed
|
* [`POST /register`](/api/register-queue), `PATCH /realm`: Removed
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in templates/zerver/api/changelog.md, as well as
|
# new level means in templates/zerver/api/changelog.md, as well as
|
||||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 133
|
API_FEATURE_LEVEL = 134
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -61,6 +61,7 @@ from zerver.models import (
|
||||||
Stream,
|
Stream,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
UserTopic,
|
||||||
get_stream_by_id_in_realm,
|
get_stream_by_id_in_realm,
|
||||||
get_system_bot,
|
get_system_bot,
|
||||||
)
|
)
|
||||||
|
@ -804,6 +805,17 @@ def do_update_message(
|
||||||
# immediate succession; this is correct only because
|
# immediate succession; this is correct only because
|
||||||
# muted_topics events always send the full set of topics.
|
# muted_topics events always send the full set of topics.
|
||||||
remove_topic_mute(muting_user, stream_being_edited.id, orig_topic_name)
|
remove_topic_mute(muting_user, stream_being_edited.id, orig_topic_name)
|
||||||
|
|
||||||
|
date_unmuted = timezone_now()
|
||||||
|
user_topic_event: Dict[str, Any] = {
|
||||||
|
"type": "user_topic",
|
||||||
|
"stream_id": stream_being_edited.id,
|
||||||
|
"topic_name": orig_topic_name,
|
||||||
|
"last_updated": datetime_to_timestamp(date_unmuted),
|
||||||
|
"visibility_policy": UserTopic.VISIBILITY_POLICY_INHERIT,
|
||||||
|
}
|
||||||
|
send_event(user_profile.realm, user_topic_event, [user_profile.id])
|
||||||
|
|
||||||
do_mute_topic(
|
do_mute_topic(
|
||||||
muting_user,
|
muting_user,
|
||||||
new_stream if new_stream is not None else stream_being_edited,
|
new_stream if new_stream is not None else stream_being_edited,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.user_topics import add_topic_mute, get_topic_mutes, remove_topic_mute
|
from zerver.lib.user_topics import add_topic_mute, get_topic_mutes, remove_topic_mute
|
||||||
from zerver.models import Stream, UserProfile, UserTopic
|
from zerver.models import Stream, UserProfile, UserTopic
|
||||||
from zerver.tornado.django_api import send_event
|
from zerver.tornado.django_api import send_event
|
||||||
|
@ -28,8 +29,22 @@ def do_mute_topic(
|
||||||
date_muted,
|
date_muted,
|
||||||
ignore_duplicate=ignore_duplicate,
|
ignore_duplicate=ignore_duplicate,
|
||||||
)
|
)
|
||||||
event = dict(type="muted_topics", muted_topics=get_topic_mutes(user_profile))
|
|
||||||
send_event(user_profile.realm, event, [user_profile.id])
|
# This first muted_topics event is deprecated and will be removed
|
||||||
|
# once clients are migrated to handle the user_topic event type
|
||||||
|
# instead.
|
||||||
|
muted_topics_event = dict(type="muted_topics", muted_topics=get_topic_mutes(user_profile))
|
||||||
|
send_event(user_profile.realm, muted_topics_event, [user_profile.id])
|
||||||
|
|
||||||
|
user_topic_event: Dict[str, Any] = {
|
||||||
|
"type": "user_topic",
|
||||||
|
"stream_id": stream.id,
|
||||||
|
"topic_name": topic,
|
||||||
|
"last_updated": datetime_to_timestamp(date_muted),
|
||||||
|
"visibility_policy": UserTopic.MUTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
send_event(user_profile.realm, user_topic_event, [user_profile.id])
|
||||||
|
|
||||||
|
|
||||||
def do_unmute_topic(user_profile: UserProfile, stream: Stream, topic: str) -> None:
|
def do_unmute_topic(user_profile: UserProfile, stream: Stream, topic: str) -> None:
|
||||||
|
@ -40,5 +55,21 @@ def do_unmute_topic(user_profile: UserProfile, stream: Stream, topic: str) -> No
|
||||||
remove_topic_mute(user_profile, stream.id, topic)
|
remove_topic_mute(user_profile, stream.id, topic)
|
||||||
except UserTopic.DoesNotExist:
|
except UserTopic.DoesNotExist:
|
||||||
raise JsonableError(_("Topic is not muted"))
|
raise JsonableError(_("Topic is not muted"))
|
||||||
event = dict(type="muted_topics", muted_topics=get_topic_mutes(user_profile))
|
|
||||||
send_event(user_profile.realm, event, [user_profile.id])
|
# This first muted_topics event is deprecated and will be removed
|
||||||
|
# once clients are migrated to handle the user_topic event type
|
||||||
|
# instead.
|
||||||
|
muted_topics_event = dict(type="muted_topics", muted_topics=get_topic_mutes(user_profile))
|
||||||
|
send_event(user_profile.realm, muted_topics_event, [user_profile.id])
|
||||||
|
|
||||||
|
date_unmuted = timezone_now()
|
||||||
|
|
||||||
|
user_topic_event: Dict[str, Any] = {
|
||||||
|
"type": "user_topic",
|
||||||
|
"stream_id": stream.id,
|
||||||
|
"topic_name": topic,
|
||||||
|
"last_updated": datetime_to_timestamp(date_unmuted),
|
||||||
|
"visibility_policy": UserTopic.VISIBILITY_POLICY_INHERIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
send_event(user_profile.realm, user_topic_event, [user_profile.id])
|
||||||
|
|
|
@ -343,6 +343,19 @@ muted_topics_event = event_dict_type(
|
||||||
)
|
)
|
||||||
check_muted_topics = make_checker(muted_topics_event)
|
check_muted_topics = make_checker(muted_topics_event)
|
||||||
|
|
||||||
|
user_topic_event = DictType(
|
||||||
|
required_keys=[
|
||||||
|
("id", int),
|
||||||
|
("type", Equals("user_topic")),
|
||||||
|
("stream_id", int),
|
||||||
|
("topic_name", str),
|
||||||
|
("last_updated", int),
|
||||||
|
("visibility_policy", int),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
check_user_topic = make_checker(user_topic_event)
|
||||||
|
|
||||||
muted_user_type = DictType(
|
muted_user_type = DictType(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("id", int),
|
("id", int),
|
||||||
|
|
|
@ -49,7 +49,7 @@ from zerver.lib.topic import TOPIC_NAME
|
||||||
from zerver.lib.user_groups import user_groups_in_realm_serialized
|
from zerver.lib.user_groups import user_groups_in_realm_serialized
|
||||||
from zerver.lib.user_mutes import get_user_mutes
|
from zerver.lib.user_mutes import get_user_mutes
|
||||||
from zerver.lib.user_status import get_user_info_dict
|
from zerver.lib.user_status import get_user_info_dict
|
||||||
from zerver.lib.user_topics import get_topic_mutes
|
from zerver.lib.user_topics import get_topic_mutes, get_user_topics
|
||||||
from zerver.lib.users import get_cross_realm_dicts, get_raw_user_data, is_administrator_role
|
from zerver.lib.users import get_cross_realm_dicts, get_raw_user_data, is_administrator_role
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
MAX_TOPIC_NAME_LENGTH,
|
MAX_TOPIC_NAME_LENGTH,
|
||||||
|
@ -63,6 +63,7 @@ from zerver.models import (
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
UserStatus,
|
UserStatus,
|
||||||
|
UserTopic,
|
||||||
custom_profile_fields_for_realm,
|
custom_profile_fields_for_realm,
|
||||||
get_default_stream_groups,
|
get_default_stream_groups,
|
||||||
get_realm_domains,
|
get_realm_domains,
|
||||||
|
@ -192,7 +193,13 @@ def fetch_initial_state_data(
|
||||||
state["drafts"] = user_draft_dicts
|
state["drafts"] = user_draft_dicts
|
||||||
|
|
||||||
if want("muted_topics"):
|
if want("muted_topics"):
|
||||||
state["muted_topics"] = [] if user_profile is None else get_topic_mutes(user_profile)
|
# Suppress muted_topics data for clients that explicitly
|
||||||
|
# support user_topic. This allows clients to request both the
|
||||||
|
# user_topic and muted_topics, and receive the duplicate
|
||||||
|
# muted_topics data only from older servers that don't yet
|
||||||
|
# support user_topic.
|
||||||
|
if event_types is None or not want("user_topic"):
|
||||||
|
state["muted_topics"] = [] if user_profile is None else get_topic_mutes(user_profile)
|
||||||
|
|
||||||
if want("muted_users"):
|
if want("muted_users"):
|
||||||
state["muted_users"] = [] if user_profile is None else get_user_mutes(user_profile)
|
state["muted_users"] = [] if user_profile is None else get_user_mutes(user_profile)
|
||||||
|
@ -584,6 +591,9 @@ def fetch_initial_state_data(
|
||||||
# We require creating an account to access statuses.
|
# We require creating an account to access statuses.
|
||||||
state["user_status"] = {} if user_profile is None else get_user_info_dict(realm_id=realm.id)
|
state["user_status"] = {} if user_profile is None else get_user_info_dict(realm_id=realm.id)
|
||||||
|
|
||||||
|
if want("user_topic"):
|
||||||
|
state["user_topics"] = [] if user_profile is None else get_user_topics(user_profile)
|
||||||
|
|
||||||
if want("video_calls"):
|
if want("video_calls"):
|
||||||
state["has_zoom_token"] = settings_user.zoom_token is not None
|
state["has_zoom_token"] = settings_user.zoom_token is not None
|
||||||
|
|
||||||
|
@ -1319,6 +1329,19 @@ def apply_event(
|
||||||
user_status.pop(user_id_str, None)
|
user_status.pop(user_id_str, None)
|
||||||
|
|
||||||
state["user_status"] = user_status
|
state["user_status"] = user_status
|
||||||
|
elif event["type"] == "user_topic":
|
||||||
|
if event["visibility_policy"] == UserTopic.VISIBILITY_POLICY_INHERIT:
|
||||||
|
user_topics_state = state["user_topics"]
|
||||||
|
for i in range(len(user_topics_state)):
|
||||||
|
if (
|
||||||
|
user_topics_state[i]["stream_id"] == event["stream_id"]
|
||||||
|
and user_topics_state[i]["topic_name"] == event["topic_name"]
|
||||||
|
):
|
||||||
|
del user_topics_state[i]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
fields = ["stream_id", "topic_name", "visibility_policy", "last_updated"]
|
||||||
|
state["user_topics"].append({x: event[x] for x in fields})
|
||||||
elif event["type"] == "has_zoom_token":
|
elif event["type"] == "has_zoom_token":
|
||||||
state["has_zoom_token"] = event["value"]
|
state["has_zoom_token"] = event["value"]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -15,12 +15,15 @@ from zerver.models import UserProfile, UserTopic, get_stream
|
||||||
def get_user_topics(
|
def get_user_topics(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
include_deactivated: bool = False,
|
include_deactivated: bool = False,
|
||||||
|
include_stream_name: bool = False,
|
||||||
visibility_policy: Optional[int] = None,
|
visibility_policy: Optional[int] = None,
|
||||||
) -> List[UserTopicDict]:
|
) -> List[UserTopicDict]:
|
||||||
"""
|
"""
|
||||||
Fetches UserTopic objects associated with the target user.
|
Fetches UserTopic objects associated with the target user.
|
||||||
* include_deactivated: Whether to include those associated with
|
* include_deactivated: Whether to include those associated with
|
||||||
deactivated streams.
|
deactivated streams.
|
||||||
|
* include_stream_name: Whether to include stream names in the
|
||||||
|
returned dictionaries.
|
||||||
* visibility_policy: If specified, returns only UserTopic objects
|
* visibility_policy: If specified, returns only UserTopic objects
|
||||||
with the specified visibility_policy value.
|
with the specified visibility_policy value.
|
||||||
"""
|
"""
|
||||||
|
@ -40,6 +43,9 @@ def get_user_topics(
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
if not include_stream_name:
|
||||||
|
del row["stream__name"]
|
||||||
|
|
||||||
row["last_updated"] = datetime_to_timestamp(row["last_updated"])
|
row["last_updated"] = datetime_to_timestamp(row["last_updated"])
|
||||||
result.append(row)
|
result.append(row)
|
||||||
|
|
||||||
|
@ -53,6 +59,7 @@ def get_topic_mutes(
|
||||||
user_topics = get_user_topics(
|
user_topics = get_user_topics(
|
||||||
user_profile=user_profile,
|
user_profile=user_profile,
|
||||||
include_deactivated=include_deactivated,
|
include_deactivated=include_deactivated,
|
||||||
|
include_stream_name=True,
|
||||||
visibility_policy=UserTopic.MUTED,
|
visibility_policy=UserTopic.MUTED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 4.0.6 on 2022-08-01 20:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0401_migrate_old_realm_reactivation_links"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="usertopic",
|
||||||
|
name="visibility_policy",
|
||||||
|
field=models.SmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, "Muted topic"),
|
||||||
|
(2, "Unmuted topic in muted stream"),
|
||||||
|
(3, "Followed topic"),
|
||||||
|
(0, "User's default policy for the stream."),
|
||||||
|
],
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2545,6 +2545,9 @@ class UserTopic(models.Model):
|
||||||
# Implicitly, if a UserTopic does not exist, the (user, topic)
|
# Implicitly, if a UserTopic does not exist, the (user, topic)
|
||||||
# pair should have normal behavior for that (user, stream) pair.
|
# pair should have normal behavior for that (user, stream) pair.
|
||||||
|
|
||||||
|
# We use this in our code to represent the condition in the comment above.
|
||||||
|
VISIBILITY_POLICY_INHERIT = 0
|
||||||
|
|
||||||
# A normal muted topic. No notifications and unreads hidden.
|
# A normal muted topic. No notifications and unreads hidden.
|
||||||
MUTED = 1
|
MUTED = 1
|
||||||
|
|
||||||
|
@ -2559,6 +2562,7 @@ class UserTopic(models.Model):
|
||||||
(MUTED, "Muted topic"),
|
(MUTED, "Muted topic"),
|
||||||
(UNMUTED, "Unmuted topic in muted stream"),
|
(UNMUTED, "Unmuted topic in muted stream"),
|
||||||
(FOLLOWED, "Followed topic"),
|
(FOLLOWED, "Followed topic"),
|
||||||
|
(VISIBILITY_POLICY_INHERIT, "User's default policy for the stream."),
|
||||||
)
|
)
|
||||||
|
|
||||||
visibility_policy: int = models.SmallIntegerField(
|
visibility_policy: int = models.SmallIntegerField(
|
||||||
|
|
|
@ -1886,6 +1886,7 @@ paths:
|
||||||
- muted_topics
|
- muted_topics
|
||||||
muted_topics:
|
muted_topics:
|
||||||
type: array
|
type: array
|
||||||
|
deprecated: true
|
||||||
description: |
|
description: |
|
||||||
Array of tuples, where each tuple describes a muted topic.
|
Array of tuples, where each tuple describes a muted topic.
|
||||||
The first element of the tuple is the stream name in which the topic
|
The first element of the tuple is the stream name in which the topic
|
||||||
|
@ -1893,6 +1894,12 @@ paths:
|
||||||
and the third element is an integer UNIX timestamp representing
|
and the third element is an integer UNIX timestamp representing
|
||||||
when the topic was muted.
|
when the topic was muted.
|
||||||
|
|
||||||
|
**Changes**: Deprecated in Zulip 6.0 (feature level
|
||||||
|
134). Starting with this version, clients that explicitly
|
||||||
|
requested the replacement `user_topic` event type when
|
||||||
|
registering their event queue will not receive this legacy
|
||||||
|
event type.
|
||||||
|
|
||||||
**Changes**: Before Zulip 3.0 (feature level 1), the `muted_topics`
|
**Changes**: Before Zulip 3.0 (feature level 1), the `muted_topics`
|
||||||
array objects were 2-item tuples and did not include the timestamp
|
array objects were 2-item tuples and did not include the timestamp
|
||||||
information for when the topic was muted.
|
information for when the topic was muted.
|
||||||
|
@ -1910,6 +1917,57 @@ paths:
|
||||||
[["Denmark", "topic", 1594825442]],
|
[["Denmark", "topic", 1594825442]],
|
||||||
"id": 0,
|
"id": 0,
|
||||||
}
|
}
|
||||||
|
- type: object
|
||||||
|
description: |
|
||||||
|
Event sent to a user's clients when the user mutes/unmutes
|
||||||
|
a topic, or otherwise modifies their personal per-topic
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 6.0 (feature level 134). Previously,
|
||||||
|
clients were notified about changes in muted topic
|
||||||
|
configuration via the `muted_topics` event type.
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/EventIdSchema"
|
||||||
|
type:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/EventTypeSchema"
|
||||||
|
- enum:
|
||||||
|
- user_topic
|
||||||
|
stream_id:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
The ID of the stream to which the topic belongs.
|
||||||
|
topic_name:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The name of the topic.
|
||||||
|
last_updated:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
An integer UNIX timestamp representing when the user-topic
|
||||||
|
relationship was last changed.
|
||||||
|
visibility_policy:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
An integer indicating the user's visibility
|
||||||
|
preferences for the topic, such as whether the topic
|
||||||
|
is muted.
|
||||||
|
|
||||||
|
- 0 = None. Used in events to indicate that the user no
|
||||||
|
longer has a special visibility policy for this
|
||||||
|
topic (for example, the user just unmuted it).
|
||||||
|
- 1 = Muted. Used to record muted topics.
|
||||||
|
additionalProperties: false
|
||||||
|
example:
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "user_topic",
|
||||||
|
"stream_id": 1,
|
||||||
|
"topic_name": "topic",
|
||||||
|
"last_updated": 1594825442,
|
||||||
|
"visibility_policy": 1,
|
||||||
|
}
|
||||||
- type: object
|
- type: object
|
||||||
description: |
|
description: |
|
||||||
Event sent to a user's clients when that user's set of
|
Event sent to a user's clients when that user's set of
|
||||||
|
@ -9332,6 +9390,7 @@ paths:
|
||||||
this always had value 10000.
|
this always had value 10000.
|
||||||
muted_topics:
|
muted_topics:
|
||||||
type: array
|
type: array
|
||||||
|
deprecated: true
|
||||||
description: |
|
description: |
|
||||||
Present if `muted_topics` is present in `fetch_event_types`.
|
Present if `muted_topics` is present in `fetch_event_types`.
|
||||||
|
|
||||||
|
@ -9341,6 +9400,11 @@ paths:
|
||||||
and the third element is an integer UNIX timestamp representing
|
and the third element is an integer UNIX timestamp representing
|
||||||
when the topic was muted.
|
when the topic was muted.
|
||||||
|
|
||||||
|
**Changes**: Deprecated in Zulip 6.0 (feature level 134). Starting
|
||||||
|
with this version, `muted_topics` will only be present in the
|
||||||
|
response if the `user_topic` object, which generalizes and replaces
|
||||||
|
this field, is not explicitly requested via `fetch_event_types`.
|
||||||
|
|
||||||
**Changes**: Before Zulip 3.0 (feature level 1), the `muted_topics`
|
**Changes**: Before Zulip 3.0 (feature level 1), the `muted_topics`
|
||||||
array objects were 2-item tuples and did not include the timestamp
|
array objects were 2-item tuples and did not include the timestamp
|
||||||
information for when the topic was muted.
|
information for when the topic was muted.
|
||||||
|
@ -10192,6 +10256,39 @@ paths:
|
||||||
read messages.
|
read messages.
|
||||||
|
|
||||||
**Changes**: New in Zulip 5.0 (feature level 105).
|
**Changes**: New in Zulip 5.0 (feature level 105).
|
||||||
|
user_topics:
|
||||||
|
type: array
|
||||||
|
description: |
|
||||||
|
Present if `user_topic` is present in `fetch_event_types`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 6.0 (feature level 134), deprecating and
|
||||||
|
replacing the previous `muted_topics` structure.
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Object describing the user's configuration for a given topic.
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
stream_id:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
The ID of the stream to which the topic belongs.
|
||||||
|
topic_name:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The name of the topic.
|
||||||
|
last_updated:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
An integer UNIX timestamp representing when the user-topic
|
||||||
|
relationship was changed.
|
||||||
|
visibility_policy:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
An integer indicating the user's visibility configuration for
|
||||||
|
the topic.
|
||||||
|
|
||||||
|
- 1 = Muted. Used to record [muted topics](/help/mute-a-topic).
|
||||||
has_zoom_token:
|
has_zoom_token:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -1051,7 +1051,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
with mock.patch("zerver.lib.events.always_want") as want_mock:
|
with mock.patch("zerver.lib.events.always_want") as want_mock:
|
||||||
fetch_initial_state_data(user)
|
fetch_initial_state_data(user)
|
||||||
|
|
||||||
self.assert_length(queries, 36)
|
self.assert_length(queries, 37)
|
||||||
|
|
||||||
expected_counts = dict(
|
expected_counts = dict(
|
||||||
alert_words=1,
|
alert_words=1,
|
||||||
|
@ -1086,6 +1086,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
update_message_flags=5,
|
update_message_flags=5,
|
||||||
user_settings=0,
|
user_settings=0,
|
||||||
user_status=1,
|
user_status=1,
|
||||||
|
user_topic=1,
|
||||||
video_calls=0,
|
video_calls=0,
|
||||||
giphy=0,
|
giphy=0,
|
||||||
)
|
)
|
||||||
|
|
|
@ -181,6 +181,7 @@ from zerver.lib.event_schema import (
|
||||||
check_user_group_update,
|
check_user_group_update,
|
||||||
check_user_settings_update,
|
check_user_settings_update,
|
||||||
check_user_status,
|
check_user_status,
|
||||||
|
check_user_topic,
|
||||||
)
|
)
|
||||||
from zerver.lib.events import (
|
from zerver.lib.events import (
|
||||||
RestartEventException,
|
RestartEventException,
|
||||||
|
@ -1332,11 +1333,23 @@ class NormalActionsTest(BaseAction):
|
||||||
|
|
||||||
def test_muted_topics_events(self) -> None:
|
def test_muted_topics_events(self) -> None:
|
||||||
stream = get_stream("Denmark", self.user_profile.realm)
|
stream = get_stream("Denmark", self.user_profile.realm)
|
||||||
events = self.verify_action(lambda: do_mute_topic(self.user_profile, stream, "topic"))
|
events = self.verify_action(
|
||||||
|
lambda: do_mute_topic(self.user_profile, stream, "topic"), num_events=2
|
||||||
|
)
|
||||||
check_muted_topics("events[0]", events[0])
|
check_muted_topics("events[0]", events[0])
|
||||||
|
check_user_topic("events[1]", events[1])
|
||||||
|
|
||||||
events = self.verify_action(lambda: do_unmute_topic(self.user_profile, stream, "topic"))
|
events = self.verify_action(
|
||||||
|
lambda: do_unmute_topic(self.user_profile, stream, "topic"), num_events=2
|
||||||
|
)
|
||||||
check_muted_topics("events[0]", events[0])
|
check_muted_topics("events[0]", events[0])
|
||||||
|
check_user_topic("events[1]", events[1])
|
||||||
|
|
||||||
|
events = self.verify_action(
|
||||||
|
lambda: do_mute_topic(self.user_profile, stream, "topic"),
|
||||||
|
event_types=["muted_topics", "user_topic"],
|
||||||
|
)
|
||||||
|
check_user_topic("events[0]", events[0])
|
||||||
|
|
||||||
def test_muted_users_events(self) -> None:
|
def test_muted_users_events(self) -> None:
|
||||||
muted_user = self.example_user("othello")
|
muted_user = self.example_user("othello")
|
||||||
|
|
|
@ -213,6 +213,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"user_id",
|
"user_id",
|
||||||
"user_settings",
|
"user_settings",
|
||||||
"user_status",
|
"user_status",
|
||||||
|
"user_topics",
|
||||||
"warn_no_email",
|
"warn_no_email",
|
||||||
"webpack_public_path",
|
"webpack_public_path",
|
||||||
"zulip_feature_level",
|
"zulip_feature_level",
|
||||||
|
@ -250,7 +251,7 @@ class HomeTest(ZulipTestCase):
|
||||||
set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
|
set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assert_length(queries, 46)
|
self.assert_length(queries, 47)
|
||||||
self.assert_length(cache_mock.call_args_list, 5)
|
self.assert_length(cache_mock.call_args_list, 5)
|
||||||
|
|
||||||
html = result.content.decode()
|
html = result.content.decode()
|
||||||
|
@ -394,7 +395,7 @@ class HomeTest(ZulipTestCase):
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
self.check_rendered_logged_in_app(result)
|
self.check_rendered_logged_in_app(result)
|
||||||
self.assert_length(cache_mock.call_args_list, 6)
|
self.assert_length(cache_mock.call_args_list, 6)
|
||||||
self.assert_length(queries, 43)
|
self.assert_length(queries, 44)
|
||||||
|
|
||||||
def test_num_queries_with_streams(self) -> None:
|
def test_num_queries_with_streams(self) -> None:
|
||||||
main_user = self.example_user("hamlet")
|
main_user = self.example_user("hamlet")
|
||||||
|
@ -425,7 +426,7 @@ class HomeTest(ZulipTestCase):
|
||||||
with queries_captured() as queries2:
|
with queries_captured() as queries2:
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
|
|
||||||
self.assert_length(queries2, 41)
|
self.assert_length(queries2, 42)
|
||||||
|
|
||||||
# Do a sanity check that our new streams were in the payload.
|
# Do a sanity check that our new streams were in the payload.
|
||||||
html = result.content.decode()
|
html = result.content.decode()
|
||||||
|
|
|
@ -206,8 +206,16 @@ class ClientDescriptor:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def accepts_event(self, event: Mapping[str, Any]) -> bool:
|
def accepts_event(self, event: Mapping[str, Any]) -> bool:
|
||||||
if self.event_types is not None and event["type"] not in self.event_types:
|
if self.event_types is not None:
|
||||||
return False
|
if event["type"] not in self.event_types:
|
||||||
|
return False
|
||||||
|
if event["type"] == "muted_topics" and "user_topic" in self.event_types:
|
||||||
|
# Suppress muted_topics events for clients that
|
||||||
|
# support user_topic. This allows clients to request
|
||||||
|
# both the user_topic and muted_topics event types,
|
||||||
|
# and receive the duplicate muted_topics data only on
|
||||||
|
# older servers that don't support user_topic.
|
||||||
|
return False
|
||||||
if event["type"] == "message":
|
if event["type"] == "message":
|
||||||
return self.narrow_filter(event)
|
return self.narrow_filter(event)
|
||||||
if event["type"] == "typing" and "stream_id" in event:
|
if event["type"] == "typing" and "stream_id" in event:
|
||||||
|
|
Loading…
Reference in New Issue