Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Track notification counts per thread (implement MSC3773) #13181

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2c7a568
Extract the thread ID when processing push rules.
clokep Jun 14, 2022
dfd921d
Return thread notification counts down sync.
clokep Jun 16, 2022
e0ed95a
Add an experimental config option.
clokep Jul 6, 2022
d56296a
Add a sync flag for unread thread notifications
clokep Jul 28, 2022
18ea92b
Reset the notif/unread counts for all summaries before updating the c…
clokep Aug 5, 2022
8978bb7
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Aug 15, 2022
dd96e07
Sync tests with non-thread version.
clokep Aug 16, 2022
9349642
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Aug 16, 2022
2e85ec6
Make thread_id nullable.
clokep Aug 18, 2022
15afd70
Properly count the number of items cached by get_unread_event_push_ac…
clokep Aug 19, 2022
621b300
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Aug 19, 2022
42e6da0
Tweaks to index on nulls.
clokep Aug 25, 2022
9ae86cb
Fix join when rotating notifications.
clokep Aug 25, 2022
b6e0e68
Add a where clause when upserting with nulls.
clokep Aug 25, 2022
000fed4
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Aug 25, 2022
6dcf16d
Remove an XXX comment -- this should be fine.
clokep Aug 25, 2022
430cc0b
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Aug 30, 2022
4c21565
Add a versions flag.
clokep Aug 31, 2022
15bdb62
Use an unstable identifier in the sync response.
clokep Aug 31, 2022
dbb1df3
Use unstable prefixes in filters.
clokep Aug 31, 2022
247132d
Merge branch 'develop' into clokep/thread-notifs
erikjohnston Sep 2, 2022
7d29206
Fix tests.
clokep Sep 6, 2022
b789c00
Merge remote-tracking branch 'origin/develop' into clokep/thread-notifs
clokep Sep 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13181.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Experimental support for thread-specific notifications ([MSC3773](https://github.com/matrix-org/matrix-spec-proposals/pull/3773)).
7 changes: 7 additions & 0 deletions synapse/api/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"contains_url": {"type": "boolean"},
"lazy_load_members": {"type": "boolean"},
"include_redundant_members": {"type": "boolean"},
"org.matrix.msc3773.unread_thread_notifications": {"type": "boolean"},
# Include or exclude events with the provided labels.
# cf https://github.com/matrix-org/matrix-doc/pull/2326
"org.matrix.labels": {"type": "array", "items": {"type": "string"}},
Expand Down Expand Up @@ -240,6 +241,9 @@ def lazy_load_members(self) -> bool:
def include_redundant_members(self) -> bool:
return self._room_state_filter.include_redundant_members

def unread_thread_notifications(self) -> bool:
return self._room_timeline_filter.unread_thread_notifications

async def filter_presence(
self, events: Iterable[UserPresenceState]
) -> List[UserPresenceState]:
Expand Down Expand Up @@ -304,6 +308,9 @@ def __init__(self, hs: "HomeServer", filter_json: JsonDict):
self.include_redundant_members = filter_json.get(
"include_redundant_members", False
)
self.unread_thread_notifications = filter_json.get(
"org.matrix.msc3773.unread_thread_notifications", False
)

self.types = filter_json.get("types", None)
self.not_types = filter_json.get("not_types", [])
Expand Down
2 changes: 2 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:

# MSC3772: A push rule for mutual relations.
self.msc3772_enabled: bool = experimental.get("msc3772_enabled", False)
# MSC3773: Thread notifications
self.msc3773_enabled: bool = experimental.get("msc3773_enabled", False)

# MSC3715: dir param on /relations.
self.msc3715_enabled: bool = experimental.get("msc3715_enabled", False)
Expand Down
40 changes: 35 additions & 5 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from synapse.logging.context import current_context
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, start_active_span
from synapse.push.clientformat import format_push_rules_for_user
from synapse.storage.databases.main.event_push_actions import NotifCounts
from synapse.storage.databases.main.event_push_actions import RoomNotifCounts
from synapse.storage.roommember import MemberSummary
from synapse.storage.state import StateFilter
from synapse.types import (
Expand Down Expand Up @@ -127,6 +127,7 @@ class JoinedSyncResult:
ephemeral: List[JsonDict]
account_data: List[JsonDict]
unread_notifications: JsonDict
unread_thread_notifications: JsonDict
summary: Optional[JsonDict]
unread_count: int

Expand Down Expand Up @@ -277,6 +278,8 @@ def __init__(self, hs: "HomeServer"):

self.rooms_to_exclude = hs.config.server.rooms_to_exclude_from_sync

self._msc3773_enabled = hs.config.experimental.msc3773_enabled

async def wait_for_sync_for_user(
self,
requester: Requester,
Expand Down Expand Up @@ -1271,7 +1274,7 @@ async def _find_missing_partial_state_memberships(

async def unread_notifs_for_room_id(
self, room_id: str, sync_config: SyncConfig
) -> NotifCounts:
) -> RoomNotifCounts:
with Measure(self.clock, "unread_notifs_for_room_id"):

return await self.store.get_unread_event_push_actions_by_room_for_user(
Expand Down Expand Up @@ -2343,17 +2346,44 @@ async def _generate_room_entry(
ephemeral=ephemeral,
account_data=account_data_events,
unread_notifications=unread_notifications,
unread_thread_notifications={},
summary=summary,
unread_count=0,
)

if room_sync or always_include:
notifs = await self.unread_notifs_for_room_id(room_id, sync_config)

unread_notifications["notification_count"] = notifs.notify_count
unread_notifications["highlight_count"] = notifs.highlight_count
# Notifications for the main timeline.
notify_count = notifs.main_timeline.notify_count
highlight_count = notifs.main_timeline.highlight_count
unread_count = notifs.main_timeline.unread_count

room_sync.unread_count = notifs.unread_count
# Check the sync configuration.
if (
self._msc3773_enabled
and sync_config.filter_collection.unread_thread_notifications()
):
# And add info for each thread.
room_sync.unread_thread_notifications = {
thread_id: {
"notification_count": thread_notifs.notify_count,
"highlight_count": thread_notifs.highlight_count,
}
for thread_id, thread_notifs in notifs.threads.items()
if thread_id is not None
}

else:
# Combine the unread counts for all threads and main timeline.
for thread_notifs in notifs.threads.values():
notify_count += thread_notifs.notify_count
highlight_count += thread_notifs.highlight_count
unread_count += thread_notifs.unread_count

unread_notifications["notification_count"] = notify_count
unread_notifications["highlight_count"] = highlight_count
room_sync.unread_count = unread_count

sync_result_builder.joined.append(room_sync)

Expand Down
29 changes: 15 additions & 14 deletions synapse/push/bulk_push_rule_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,15 @@ async def _get_power_levels_and_sender_level(
return pl_event.content if pl_event else {}, sender_level

async def _get_mutual_relations(
self, event: EventBase, rules: Iterable[Tuple[PushRule, bool]]
self, parent_id: str, rules: Iterable[Tuple[PushRule, bool]]
) -> Dict[str, Set[Tuple[str, str]]]:
"""
Fetch event metadata for events which related to the same event as the given event.

If the given event has no relation information, returns an empty dictionary.

Args:
event_id: The event ID which is targeted by relations.
parent_id: The event ID which is targeted by relations.
rules: The push rules which will be processed for this event.

Returns:
Expand All @@ -220,12 +220,6 @@ async def _get_mutual_relations(
if not self._relations_match_enabled:
return {}

# If the event does not have a relation, then cannot have any mutual
# relations.
relation = relation_from_event(event)
if not relation:
return {}

# Pre-filter to figure out which relation types are interesting.
rel_types = set()
for rule, enabled in rules:
Expand All @@ -246,9 +240,7 @@ async def _get_mutual_relations(
return {}

# If any valid rules were found, fetch the mutual relations.
return await self.store.get_mutual_event_relations(
relation.parent_id, rel_types
)
return await self.store.get_mutual_event_relations(parent_id, rel_types)

@measure_func("action_for_event_by_user")
async def action_for_event_by_user(
Expand Down Expand Up @@ -281,9 +273,17 @@ async def action_for_event_by_user(
sender_power_level,
) = await self._get_power_levels_and_sender_level(event, context)

relations = await self._get_mutual_relations(
event, itertools.chain(*rules_by_user.values())
)
relation = relation_from_event(event)
# If the event does not have a relation, then cannot have any mutual
# relations or thread ID.
relations = {}
thread_id = None
if relation:
relations = await self._get_mutual_relations(
relation.parent_id, itertools.chain(*rules_by_user.values())
)
if relation.rel_type == RelationTypes.THREAD:
thread_id = relation.parent_id

evaluator = PushRuleEvaluatorForEvent(
event,
Expand Down Expand Up @@ -352,6 +352,7 @@ async def action_for_event_by_user(
event.event_id,
actions_by_user,
count_as_unread,
thread_id,
)


Expand Down
9 changes: 7 additions & 2 deletions synapse/push/push_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,20 @@ async def get_badge_count(store: DataStore, user_id: str, group_by_room: bool) -
user_id,
)
)
if notifs.notify_count == 0:
# Combine the counts from all the threads.
notify_count = notifs.main_timeline.notify_count + sum(
n.notify_count for n in notifs.threads.values()
)

if notify_count == 0:
continue

if group_by_room:
# return one badge count per conversation
badge += 1
else:
# increment the badge count by the number of unread messages in the room
badge += notifs.notify_count
badge += notify_count
return badge


Expand Down
4 changes: 4 additions & 0 deletions synapse/rest/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,10 @@ async def encode_room(
ephemeral_events = room.ephemeral
result["ephemeral"] = {"events": ephemeral_events}
result["unread_notifications"] = room.unread_notifications
if room.unread_thread_notifications:
result[
"org.matrix.msc3773.unread_thread_notifications"
] = room.unread_thread_notifications
result["summary"] = room.summary
if self._msc2654_enabled:
result["org.matrix.msc2654.unread_count"] = room.unread_count
Expand Down
2 changes: 2 additions & 0 deletions synapse/rest/client/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
"org.matrix.msc3030": self.config.experimental.msc3030_enabled,
# Adds support for thread relations, per MSC3440.
"org.matrix.msc3440.stable": True, # TODO: remove when "v1.3" is added above
# Support for thread notification counts.
"org.matrix.msc3773": self.config.experimental.msc3773_enabled,
# Allows moderators to fetch redacted event content as described in MSC2815
"fi.mau.msc2815": self.config.experimental.msc2815_enabled,
},
Expand Down
2 changes: 1 addition & 1 deletion synapse/storage/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"event_search": "event_search_event_id_idx",
"local_media_repository_thumbnails": "local_media_repository_thumbnails_method_idx",
"remote_media_cache_thumbnails": "remote_media_repository_thumbnails_method_idx",
"event_push_summary": "event_push_summary_unique_index",
"event_push_summary": "event_push_summary_unique_index_null",
}


Expand Down
Loading