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

Commit b4ec4f5

Browse files
authored
Track notification counts per thread (implement MSC3773). (#13776)
When retrieving counts of notifications segment the results based on the thread ID, but choose whether to return them as individual threads or as a single summed field by letting the client opt-in via a sync flag. The summarization code is also updated to be per thread, instead of per room.
1 parent 94017e8 commit b4ec4f5

File tree

17 files changed

+514
-93
lines changed

17 files changed

+514
-93
lines changed

changelog.d/13776.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Experimental support for thread-specific notifications ([MSC3773](https://github.com/matrix-org/matrix-spec-proposals/pull/3773)).

synapse/api/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
# the maximum length for a user id is 255 characters
3232
MAX_USERID_LENGTH = 255
3333

34+
# Constant value used for the pseudo-thread which is the main timeline.
35+
MAIN_TIMELINE: Final = "main"
36+
3437

3538
class Membership:
3639

synapse/api/filtering.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"contains_url": {"type": "boolean"},
8585
"lazy_load_members": {"type": "boolean"},
8686
"include_redundant_members": {"type": "boolean"},
87+
"org.matrix.msc3773.unread_thread_notifications": {"type": "boolean"},
8788
# Include or exclude events with the provided labels.
8889
# cf https://github.com/matrix-org/matrix-doc/pull/2326
8990
"org.matrix.labels": {"type": "array", "items": {"type": "string"}},
@@ -240,6 +241,9 @@ def lazy_load_members(self) -> bool:
240241
def include_redundant_members(self) -> bool:
241242
return self._room_state_filter.include_redundant_members
242243

244+
def unread_thread_notifications(self) -> bool:
245+
return self._room_timeline_filter.unread_thread_notifications
246+
243247
async def filter_presence(
244248
self, events: Iterable[UserPresenceState]
245249
) -> List[UserPresenceState]:
@@ -304,6 +308,12 @@ def __init__(self, hs: "HomeServer", filter_json: JsonDict):
304308
self.include_redundant_members = filter_json.get(
305309
"include_redundant_members", False
306310
)
311+
if hs.config.experimental.msc3773_enabled:
312+
self.unread_thread_notifications: bool = filter_json.get(
313+
"org.matrix.msc3773.unread_thread_notifications", False
314+
)
315+
else:
316+
self.unread_thread_notifications = False
307317

308318
self.types = filter_json.get("types", None)
309319
self.not_types = filter_json.get("not_types", [])

synapse/config/experimental.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
9999
self.msc3771_enabled: bool = experimental.get("msc3771_enabled", False)
100100
# MSC3772: A push rule for mutual relations.
101101
self.msc3772_enabled: bool = experimental.get("msc3772_enabled", False)
102+
# MSC3773: Thread notifications
103+
self.msc3773_enabled: bool = experimental.get("msc3773_enabled", False)
102104

103105
# MSC3715: dir param on /relations.
104106
self.msc3715_enabled: bool = experimental.get("msc3715_enabled", False)

synapse/handlers/sync.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from synapse.logging.context import current_context
4141
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, start_active_span
4242
from synapse.push.clientformat import format_push_rules_for_user
43-
from synapse.storage.databases.main.event_push_actions import NotifCounts
43+
from synapse.storage.databases.main.event_push_actions import RoomNotifCounts
4444
from synapse.storage.roommember import MemberSummary
4545
from synapse.storage.state import StateFilter
4646
from synapse.types import (
@@ -128,6 +128,7 @@ class JoinedSyncResult:
128128
ephemeral: List[JsonDict]
129129
account_data: List[JsonDict]
130130
unread_notifications: JsonDict
131+
unread_thread_notifications: JsonDict
131132
summary: Optional[JsonDict]
132133
unread_count: int
133134

@@ -278,6 +279,8 @@ def __init__(self, hs: "HomeServer"):
278279

279280
self.rooms_to_exclude = hs.config.server.rooms_to_exclude_from_sync
280281

282+
self._msc3773_enabled = hs.config.experimental.msc3773_enabled
283+
281284
async def wait_for_sync_for_user(
282285
self,
283286
requester: Requester,
@@ -1288,7 +1291,7 @@ async def _find_missing_partial_state_memberships(
12881291

12891292
async def unread_notifs_for_room_id(
12901293
self, room_id: str, sync_config: SyncConfig
1291-
) -> NotifCounts:
1294+
) -> RoomNotifCounts:
12921295
with Measure(self.clock, "unread_notifs_for_room_id"):
12931296

12941297
return await self.store.get_unread_event_push_actions_by_room_for_user(
@@ -2353,17 +2356,44 @@ async def _generate_room_entry(
23532356
ephemeral=ephemeral,
23542357
account_data=account_data_events,
23552358
unread_notifications=unread_notifications,
2359+
unread_thread_notifications={},
23562360
summary=summary,
23572361
unread_count=0,
23582362
)
23592363

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

2363-
unread_notifications["notification_count"] = notifs.notify_count
2364-
unread_notifications["highlight_count"] = notifs.highlight_count
2367+
# Notifications for the main timeline.
2368+
notify_count = notifs.main_timeline.notify_count
2369+
highlight_count = notifs.main_timeline.highlight_count
2370+
unread_count = notifs.main_timeline.unread_count
23652371

2366-
room_sync.unread_count = notifs.unread_count
2372+
# Check the sync configuration.
2373+
if (
2374+
self._msc3773_enabled
2375+
and sync_config.filter_collection.unread_thread_notifications()
2376+
):
2377+
# And add info for each thread.
2378+
room_sync.unread_thread_notifications = {
2379+
thread_id: {
2380+
"notification_count": thread_notifs.notify_count,
2381+
"highlight_count": thread_notifs.highlight_count,
2382+
}
2383+
for thread_id, thread_notifs in notifs.threads.items()
2384+
if thread_id is not None
2385+
}
2386+
2387+
else:
2388+
# Combine the unread counts for all threads and main timeline.
2389+
for thread_notifs in notifs.threads.values():
2390+
notify_count += thread_notifs.notify_count
2391+
highlight_count += thread_notifs.highlight_count
2392+
unread_count += thread_notifs.unread_count
2393+
2394+
unread_notifications["notification_count"] = notify_count
2395+
unread_notifications["highlight_count"] = highlight_count
2396+
room_sync.unread_count = unread_count
23672397

23682398
sync_result_builder.joined.append(room_sync)
23692399

synapse/push/bulk_push_rule_evaluator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from prometheus_client import Counter
3333

34-
from synapse.api.constants import EventTypes, Membership, RelationTypes
34+
from synapse.api.constants import MAIN_TIMELINE, EventTypes, Membership, RelationTypes
3535
from synapse.event_auth import auth_types_for_event, get_user_power_level
3636
from synapse.events import EventBase, relation_from_event
3737
from synapse.events.snapshot import EventContext
@@ -280,7 +280,7 @@ async def action_for_event_by_user(
280280
# If the event does not have a relation, then cannot have any mutual
281281
# relations or thread ID.
282282
relations = {}
283-
thread_id = "main"
283+
thread_id = MAIN_TIMELINE
284284
if relation:
285285
relations = await self._get_mutual_relations(
286286
relation.parent_id,

synapse/push/push_tools.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,20 @@ async def get_room_unread_count(room_id: str) -> None:
3939
await concurrently_execute(get_room_unread_count, joins, 10)
4040

4141
for notifs in room_notifs:
42-
if notifs.notify_count == 0:
42+
# Combine the counts from all the threads.
43+
notify_count = notifs.main_timeline.notify_count + sum(
44+
n.notify_count for n in notifs.threads.values()
45+
)
46+
47+
if notify_count == 0:
4348
continue
4449

4550
if group_by_room:
4651
# return one badge count per conversation
4752
badge += 1
4853
else:
4954
# increment the badge count by the number of unread messages in the room
50-
badge += notifs.notify_count
55+
badge += notify_count
5156
return badge
5257

5358

synapse/rest/client/sync.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ async def encode_room(
509509
ephemeral_events = room.ephemeral
510510
result["ephemeral"] = {"events": ephemeral_events}
511511
result["unread_notifications"] = room.unread_notifications
512+
if room.unread_thread_notifications:
513+
result[
514+
"org.matrix.msc3773.unread_thread_notifications"
515+
] = room.unread_thread_notifications
512516
result["summary"] = room.summary
513517
if self._msc2654_enabled:
514518
result["org.matrix.msc2654.unread_count"] = room.unread_count

synapse/rest/client/versions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
103103
"org.matrix.msc3030": self.config.experimental.msc3030_enabled,
104104
# Adds support for thread relations, per MSC3440.
105105
"org.matrix.msc3440.stable": True, # TODO: remove when "v1.3" is added above
106-
# Support for thread read receipts.
106+
# Support for thread read receipts & notification counts.
107107
"org.matrix.msc3771": self.config.experimental.msc3771_enabled,
108+
"org.matrix.msc3773": self.config.experimental.msc3773_enabled,
108109
# Allows moderators to fetch redacted event content as described in MSC2815
109110
"fi.mau.msc2815": self.config.experimental.msc2815_enabled,
110111
# Adds support for login token requests as per MSC3882

synapse/storage/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"event_search": "event_search_event_id_idx",
9595
"local_media_repository_thumbnails": "local_media_repository_thumbnails_method_idx",
9696
"remote_media_cache_thumbnails": "remote_media_repository_thumbnails_method_idx",
97-
"event_push_summary": "event_push_summary_unique_index",
97+
"event_push_summary": "event_push_summary_unique_index2",
9898
"receipts_linearized": "receipts_linearized_unique_index",
9999
"receipts_graph": "receipts_graph_unique_index",
100100
}

0 commit comments

Comments
 (0)