Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sliding Sync: Support filtering by 'tags' / 'not_tags' in SSS #17662

Merged
merged 12 commits into from
Sep 13, 2024
1 change: 1 addition & 0 deletions changelog.d/17662.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for the `tags` and `not_tags` filters for simplified sliding sync.
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
23 changes: 21 additions & 2 deletions synapse/handlers/sliding_sync/room_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from synapse.storage.databases.main.stream import CurrentStateDeltaMembership
from synapse.storage.roommember import RoomsForUser, RoomsForUserSlidingSync
from synapse.types import (
JsonMapping,
MutableStateMap,
PersistedEventPosition,
RoomStreamToken,
Expand Down Expand Up @@ -301,6 +302,9 @@ async def _compute_interested_rooms_new_tables(
)
dm_room_ids = await self._get_dm_rooms_for_user(user_id)

# Fetch the user tags for their rooms
room_tags = await self.store.get_tags_for_user(user_id)

# Handle state resets in the from -> to token range.
state_reset_rooms = (
newly_left_room_map.keys() - room_membership_for_user_map.keys()
Expand Down Expand Up @@ -349,6 +353,7 @@ async def _compute_interested_rooms_new_tables(
list_config.filters,
to_token,
dm_room_ids,
room_tags,
)

# Find which rooms are partially stated and may need to be filtered out
Expand Down Expand Up @@ -1699,6 +1704,7 @@ async def filter_rooms_using_tables(
filters: SlidingSyncConfig.SlidingSyncList.Filters,
to_token: StreamToken,
dm_room_ids: AbstractSet[str],
room_tags: Mapping[str, Mapping[str, JsonMapping]],
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
) -> Dict[str, RoomsForUserSlidingSync]:
"""
Filter rooms based on the sync request.
Expand All @@ -1710,6 +1716,7 @@ async def filter_rooms_using_tables(
filters: Filters to apply
to_token: We filter based on the state of the room at this token
dm_room_ids: Set of room IDs which are DMs
room_tags: Mapping of room ID to tags

Returns:
A filtered dictionary of room IDs along with membership information in the
Expand Down Expand Up @@ -1795,9 +1802,21 @@ async def filter_rooms_using_tables(
# )
raise NotImplementedError()

if filters.tags is not None or filters.not_tags is not None:
if filters.tags is not None:
with start_active_span("filters.tags"):
raise NotImplementedError()
filtered_room_id_set = {
room_id
for room_id in filtered_room_id_set
if set(room_tags.get(room_id, [])) & set(filters.tags)
}

if filters.not_tags is not None:
with start_active_span("filters.not_tags"):
filtered_room_id_set = {
room_id
for room_id in filtered_room_id_set
if not set(room_tags.get(room_id, [])) & set(filters.not_tags)
}
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

# Assemble a new sync room map but only with the `filtered_room_id_set`
return {room_id: sync_room_map[room_id] for room_id in filtered_room_id_set}
Expand Down
127 changes: 125 additions & 2 deletions tests/handlers/test_sliding_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#
#
import logging
from typing import Dict, List, Optional
from typing import Dict, List, Mapping, Optional
from unittest.mock import patch

from parameterized import parameterized
Expand All @@ -45,7 +45,7 @@
from synapse.rest.client import knock, login, room
from synapse.server import HomeServer
from synapse.storage.util.id_generators import MultiWriterIdGenerator
from synapse.types import JsonDict, StreamToken, UserID
from synapse.types import JsonDict, JsonMapping, StreamToken, UserID
from synapse.types.handlers.sliding_sync import SlidingSyncConfig
from synapse.util import Clock

Expand Down Expand Up @@ -3006,6 +3006,7 @@ def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.sliding_sync_handler = self.hs.get_sliding_sync_handler()
self.store = self.hs.get_datastores().main
self.event_sources = hs.get_event_sources()
self.account_data_handler = hs.get_account_data_handler()

def _get_sync_room_ids_for_user(
self,
Expand Down Expand Up @@ -3209,6 +3210,128 @@ def test_filter_dm_rooms(self) -> None:

self.assertEqual(falsy_filtered_room_map.keys(), {room_id})

def test_filter_rooms_by_tags(self) -> None:
"""
Test `filter.tags` for rooms with given tags
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")

# Create a room with no tags
self.helper.create_room_as(user1_id, tok=user1_tok)

footag_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
bartag_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)

tag_data: Mapping[str, Mapping[str, JsonMapping]] = {
footag_room_id: {"foo": {}},
bartag_room_id: {"bar": {}},
}

after_rooms_token = self.event_sources.get_current_token()

# Get the rooms the user should be syncing with
room_membership_for_user_map = self.get_success(
self.store.get_sliding_sync_rooms_for_user(user1_id)
)

# Try with `tags=foo`
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
foo_filtered_room_map = self.get_success(
self.sliding_sync_handler.room_lists.filter_rooms_using_tables(
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
user1_id,
room_membership_for_user_map,
SlidingSyncConfig.SlidingSyncList.Filters(
tags=["foo"],
),
after_rooms_token,
set(),
tag_data,
)
)

self.assertEqual(foo_filtered_room_map.keys(), {footag_room_id})

# Try with a random tag we didn't add
foobar_filtered_room_map = self.get_success(
self.sliding_sync_handler.room_lists.filter_rooms_using_tables(
user1_id,
room_membership_for_user_map,
SlidingSyncConfig.SlidingSyncList.Filters(
tags=["flomp"],
),
after_rooms_token,
set(),
tag_data,
)
)

self.assertEqual(len(foobar_filtered_room_map), 0)

def test_filter_rooms_by_not_tags(self) -> None:
"""
Test `filter.not_tags` for excluding rooms with given tags
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")

# Create a room with no tags
untagged_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)

footag_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
bartag_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)

tag_data: Mapping[str, Mapping[str, JsonMapping]] = {
footag_room_id: {"foo": {}},
bartag_room_id: {"bar": {}},
}

after_rooms_token = self.event_sources.get_current_token()

# Get the rooms the user should be syncing with
# sync_room_map = self._get_sync_room_ids_for_user(
# UserID.from_string(user1_id),
# from_token=None,
# to_token=after_rooms_token,
# )
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

room_membership_for_user_map = self.get_success(
self.store.get_sliding_sync_rooms_for_user(user1_id)
)

# Try with `not_tags=foo`
foo_filtered_room_map = self.get_success(
self.sliding_sync_handler.room_lists.filter_rooms_using_tables(
user1_id,
room_membership_for_user_map,
SlidingSyncConfig.SlidingSyncList.Filters(
not_tags=["foo"],
),
after_rooms_token,
set(),
tag_data,
)
)

self.assertEqual(
foo_filtered_room_map.keys(), {untagged_room_id, bartag_room_id}
)

# Try with not_tags=[foo,bar]
foobar_filtered_room_map = self.get_success(
self.sliding_sync_handler.room_lists.filter_rooms_using_tables(
user1_id,
room_membership_for_user_map,
SlidingSyncConfig.SlidingSyncList.Filters(
not_tags=["foo", "bar"],
),
after_rooms_token,
set(),
tag_data,
)
)

MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(foobar_filtered_room_map.keys(), {untagged_room_id})

def test_filter_encrypted_rooms(self) -> None:
"""
Test `filter.is_encrypted` for encrypted rooms
Expand Down
Loading