MSC3772 has been abandoned.tags/v1.70.0rc1
@@ -0,0 +1 @@ | |||
Remove the experimental implementation of [MSC3772](https://github.com/matrix-org/matrix-spec-proposals/pull/3772). |
@@ -257,19 +257,6 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
default: true, | |||
default_enabled: true, | |||
}, | |||
PushRule { | |||
rule_id: Cow::Borrowed("global/underride/.org.matrix.msc3772.thread_reply"), | |||
priority_class: 1, | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::RelationMatch { | |||
rel_type: Cow::Borrowed("m.thread"), | |||
event_type_pattern: None, | |||
sender: None, | |||
sender_type: Some(Cow::Borrowed("user_id")), | |||
})]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]), | |||
default: true, | |||
default_enabled: true, | |||
}, | |||
PushRule { | |||
rule_id: Cow::Borrowed("global/underride/.m.rule.message"), | |||
priority_class: 1, | |||
@@ -12,10 +12,7 @@ | |||
// See the License for the specific language governing permissions and | |||
// limitations under the License. | |||
use std::{ | |||
borrow::Cow, | |||
collections::{BTreeMap, BTreeSet}, | |||
}; | |||
use std::collections::BTreeMap; | |||
use anyhow::{Context, Error}; | |||
use lazy_static::lazy_static; | |||
@@ -49,13 +46,6 @@ pub struct PushRuleEvaluator { | |||
/// The `notifications` section of the current power levels in the room. | |||
notification_power_levels: BTreeMap<String, i64>, | |||
/// The relations related to the event as a mapping from relation type to | |||
/// set of sender/event type 2-tuples. | |||
relations: BTreeMap<String, BTreeSet<(String, String)>>, | |||
/// Is running "relation" conditions enabled? | |||
relation_match_enabled: bool, | |||
/// The power level of the sender of the event, or None if event is an | |||
/// outlier. | |||
sender_power_level: Option<i64>, | |||
@@ -70,8 +60,6 @@ impl PushRuleEvaluator { | |||
room_member_count: u64, | |||
sender_power_level: Option<i64>, | |||
notification_power_levels: BTreeMap<String, i64>, | |||
relations: BTreeMap<String, BTreeSet<(String, String)>>, | |||
relation_match_enabled: bool, | |||
) -> Result<Self, Error> { | |||
let body = flattened_keys | |||
.get("content.body") | |||
@@ -83,8 +71,6 @@ impl PushRuleEvaluator { | |||
body, | |||
room_member_count, | |||
notification_power_levels, | |||
relations, | |||
relation_match_enabled, | |||
sender_power_level, | |||
}) | |||
} | |||
@@ -203,89 +189,11 @@ impl PushRuleEvaluator { | |||
false | |||
} | |||
} | |||
KnownCondition::RelationMatch { | |||
rel_type, | |||
event_type_pattern, | |||
sender, | |||
sender_type, | |||
} => { | |||
self.match_relations(rel_type, sender, sender_type, user_id, event_type_pattern)? | |||
} | |||
}; | |||
Ok(result) | |||
} | |||
/// Evaluates a relation condition. | |||
fn match_relations( | |||
&self, | |||
rel_type: &str, | |||
sender: &Option<Cow<str>>, | |||
sender_type: &Option<Cow<str>>, | |||
user_id: Option<&str>, | |||
event_type_pattern: &Option<Cow<str>>, | |||
) -> Result<bool, Error> { | |||
// First check if relation matching is enabled... | |||
if !self.relation_match_enabled { | |||
return Ok(false); | |||
} | |||
// ... and if there are any relations to match against. | |||
let relations = if let Some(relations) = self.relations.get(rel_type) { | |||
relations | |||
} else { | |||
return Ok(false); | |||
}; | |||
// Extract the sender pattern from the condition | |||
let sender_pattern = if let Some(sender) = sender { | |||
Some(sender.as_ref()) | |||
} else if let Some(sender_type) = sender_type { | |||
if sender_type == "user_id" { | |||
if let Some(user_id) = user_id { | |||
Some(user_id) | |||
} else { | |||
return Ok(false); | |||
} | |||
} else { | |||
warn!("Unrecognized sender_type: {sender_type}"); | |||
return Ok(false); | |||
} | |||
} else { | |||
None | |||
}; | |||
let mut sender_compiled_pattern = if let Some(pattern) = sender_pattern { | |||
Some(get_glob_matcher(pattern, GlobMatchType::Whole)?) | |||
} else { | |||
None | |||
}; | |||
let mut type_compiled_pattern = if let Some(pattern) = event_type_pattern { | |||
Some(get_glob_matcher(pattern, GlobMatchType::Whole)?) | |||
} else { | |||
None | |||
}; | |||
for (relation_sender, event_type) in relations { | |||
if let Some(pattern) = &mut sender_compiled_pattern { | |||
if !pattern.is_match(relation_sender)? { | |||
continue; | |||
} | |||
} | |||
if let Some(pattern) = &mut type_compiled_pattern { | |||
if !pattern.is_match(event_type)? { | |||
continue; | |||
} | |||
} | |||
return Ok(true); | |||
} | |||
Ok(false) | |||
} | |||
/// Evaluates a `event_match` condition. | |||
fn match_event_match( | |||
&self, | |||
@@ -359,15 +267,8 @@ impl PushRuleEvaluator { | |||
fn push_rule_evaluator() { | |||
let mut flattened_keys = BTreeMap::new(); | |||
flattened_keys.insert("content.body".to_string(), "foo bar bob hello".to_string()); | |||
let evaluator = PushRuleEvaluator::py_new( | |||
flattened_keys, | |||
10, | |||
Some(0), | |||
BTreeMap::new(), | |||
BTreeMap::new(), | |||
true, | |||
) | |||
.unwrap(); | |||
let evaluator = | |||
PushRuleEvaluator::py_new(flattened_keys, 10, Some(0), BTreeMap::new()).unwrap(); | |||
let result = evaluator.run(&FilteredPushRules::default(), None, Some("bob")); | |||
assert_eq!(result.len(), 3); | |||
@@ -275,16 +275,6 @@ pub enum KnownCondition { | |||
SenderNotificationPermission { | |||
key: Cow<'static, str>, | |||
}, | |||
#[serde(rename = "org.matrix.msc3772.relation_match")] | |||
RelationMatch { | |||
rel_type: Cow<'static, str>, | |||
#[serde(skip_serializing_if = "Option::is_none", rename = "type")] | |||
event_type_pattern: Option<Cow<'static, str>>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
sender: Option<Cow<'static, str>>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
sender_type: Option<Cow<'static, str>>, | |||
}, | |||
} | |||
impl IntoPy<PyObject> for Condition { | |||
@@ -401,21 +391,15 @@ impl PushRules { | |||
pub struct FilteredPushRules { | |||
push_rules: PushRules, | |||
enabled_map: BTreeMap<String, bool>, | |||
msc3772_enabled: bool, | |||
} | |||
#[pymethods] | |||
impl FilteredPushRules { | |||
#[new] | |||
pub fn py_new( | |||
push_rules: PushRules, | |||
enabled_map: BTreeMap<String, bool>, | |||
msc3772_enabled: bool, | |||
) -> Self { | |||
pub fn py_new(push_rules: PushRules, enabled_map: BTreeMap<String, bool>) -> Self { | |||
Self { | |||
push_rules, | |||
enabled_map, | |||
msc3772_enabled, | |||
} | |||
} | |||
@@ -430,25 +414,13 @@ impl FilteredPushRules { | |||
/// Iterates over all the rules and their enabled state, including base | |||
/// rules, in the order they should be executed in. | |||
fn iter(&self) -> impl Iterator<Item = (&PushRule, bool)> { | |||
self.push_rules | |||
.iter() | |||
.filter(|rule| { | |||
// Ignore disabled experimental push rules | |||
if !self.msc3772_enabled | |||
&& rule.rule_id == "global/underride/.org.matrix.msc3772.thread_reply" | |||
{ | |||
return false; | |||
} | |||
true | |||
}) | |||
.map(|r| { | |||
let enabled = *self | |||
.enabled_map | |||
.get(&*r.rule_id) | |||
.unwrap_or(&r.default_enabled); | |||
(r, enabled) | |||
}) | |||
self.push_rules.iter().map(|r| { | |||
let enabled = *self | |||
.enabled_map | |||
.get(&*r.rule_id) | |||
.unwrap_or(&r.default_enabled); | |||
(r, enabled) | |||
}) | |||
} | |||
} | |||
@@ -25,9 +25,7 @@ class PushRules: | |||
def rules(self) -> Collection[PushRule]: ... | |||
class FilteredPushRules: | |||
def __init__( | |||
self, push_rules: PushRules, enabled_map: Dict[str, bool], msc3772_enabled: bool | |||
): ... | |||
def __init__(self, push_rules: PushRules, enabled_map: Dict[str, bool]): ... | |||
def rules(self) -> Collection[Tuple[PushRule, bool]]: ... | |||
def get_base_rule_ids() -> Collection[str]: ... | |||
@@ -39,8 +37,6 @@ class PushRuleEvaluator: | |||
room_member_count: int, | |||
sender_power_level: Optional[int], | |||
notification_power_levels: Mapping[str, int], | |||
relations: Mapping[str, Set[Tuple[str, str]]], | |||
relation_match_enabled: bool, | |||
): ... | |||
def run( | |||
self, | |||
@@ -95,8 +95,6 @@ class ExperimentalConfig(Config): | |||
# MSC2815 (allow room moderators to view redacted event content) | |||
self.msc2815_enabled: bool = experimental.get("msc2815_enabled", False) | |||
# 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) | |||
@@ -13,18 +13,15 @@ | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
import itertools | |||
import logging | |||
from typing import ( | |||
TYPE_CHECKING, | |||
Any, | |||
Collection, | |||
Dict, | |||
Iterable, | |||
List, | |||
Mapping, | |||
Optional, | |||
Set, | |||
Tuple, | |||
Union, | |||
) | |||
@@ -38,7 +35,7 @@ from synapse.events.snapshot import EventContext | |||
from synapse.state import POWER_KEY | |||
from synapse.storage.databases.main.roommember import EventIdMembership | |||
from synapse.storage.state import StateFilter | |||
from synapse.synapse_rust.push import FilteredPushRules, PushRule, PushRuleEvaluator | |||
from synapse.synapse_rust.push import FilteredPushRules, PushRuleEvaluator | |||
from synapse.util.caches import register_cache | |||
from synapse.util.metrics import measure_func | |||
from synapse.visibility import filter_event_for_clients_with_state | |||
@@ -117,9 +114,6 @@ class BulkPushRuleEvaluator: | |||
resizable=False, | |||
) | |||
# Whether to support MSC3772 is supported. | |||
self._relations_match_enabled = self.hs.config.experimental.msc3772_enabled | |||
async def _get_rules_for_event( | |||
self, | |||
event: EventBase, | |||
@@ -200,51 +194,6 @@ class BulkPushRuleEvaluator: | |||
return pl_event.content if pl_event else {}, sender_level | |||
async def _get_mutual_relations( | |||
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: | |||
parent_id: The event ID which is targeted by relations. | |||
rules: The push rules which will be processed for this event. | |||
Returns: | |||
A dictionary of relation type to: | |||
A set of tuples of: | |||
The sender | |||
The event type | |||
""" | |||
# If the experimental feature is not enabled, skip fetching relations. | |||
if not self._relations_match_enabled: | |||
return {} | |||
# Pre-filter to figure out which relation types are interesting. | |||
rel_types = set() | |||
for rule, enabled in rules: | |||
if not enabled: | |||
continue | |||
for condition in rule.conditions: | |||
if condition["kind"] != "org.matrix.msc3772.relation_match": | |||
continue | |||
# rel_type is required. | |||
rel_type = condition.get("rel_type") | |||
if rel_type: | |||
rel_types.add(rel_type) | |||
# If no valid rules were found, no mutual relations. | |||
if not rel_types: | |||
return {} | |||
# If any valid rules were found, fetch the mutual relations. | |||
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( | |||
self, event: EventBase, context: EventContext | |||
@@ -276,16 +225,11 @@ class BulkPushRuleEvaluator: | |||
sender_power_level, | |||
) = await self._get_power_levels_and_sender_level(event, context) | |||
# Find the event's thread ID. | |||
relation = relation_from_event(event) | |||
# If the event does not have a relation, then cannot have any mutual | |||
# relations or thread ID. | |||
relations = {} | |||
# If the event does not have a relation, then it cannot have a thread ID. | |||
thread_id = MAIN_TIMELINE | |||
if relation: | |||
relations = await self._get_mutual_relations( | |||
relation.parent_id, | |||
itertools.chain(*(r.rules() for r in rules_by_user.values())), | |||
) | |||
# Recursively attempt to find the thread this event relates to. | |||
if relation.rel_type == RelationTypes.THREAD: | |||
thread_id = relation.parent_id | |||
@@ -306,8 +250,6 @@ class BulkPushRuleEvaluator: | |||
room_member_count, | |||
sender_power_level, | |||
notification_levels, | |||
relations, | |||
self._relations_match_enabled, | |||
) | |||
users = rules_by_user.keys() | |||
@@ -259,9 +259,6 @@ class CacheInvalidationWorkerStore(SQLBaseStore): | |||
self._attempt_to_invalidate_cache("get_applicable_edit", (relates_to,)) | |||
self._attempt_to_invalidate_cache("get_thread_summary", (relates_to,)) | |||
self._attempt_to_invalidate_cache("get_thread_participated", (relates_to,)) | |||
self._attempt_to_invalidate_cache( | |||
"get_mutual_event_relations_for_rel_type", (relates_to,) | |||
) | |||
async def invalidate_cache_and_stream( | |||
self, cache_name: str, keys: Tuple[Any, ...] | |||
@@ -2024,11 +2024,6 @@ class PersistEventsStore: | |||
self.store._invalidate_cache_and_stream( | |||
txn, self.store.get_thread_participated, (redacted_relates_to,) | |||
) | |||
self.store._invalidate_cache_and_stream( | |||
txn, | |||
self.store.get_mutual_event_relations_for_rel_type, | |||
(redacted_relates_to,), | |||
) | |||
self.db_pool.simple_delete_txn( | |||
txn, table="event_relations", keyvalues={"event_id": redacted_event_id} | |||
@@ -29,7 +29,6 @@ from typing import ( | |||
) | |||
from synapse.api.errors import StoreError | |||
from synapse.config.homeserver import ExperimentalConfig | |||
from synapse.replication.slave.storage._slaved_id_tracker import SlavedIdTracker | |||
from synapse.storage._base import SQLBaseStore | |||
from synapse.storage.database import ( | |||
@@ -63,9 +62,7 @@ logger = logging.getLogger(__name__) | |||
def _load_rules( | |||
rawrules: List[JsonDict], | |||
enabled_map: Dict[str, bool], | |||
experimental_config: ExperimentalConfig, | |||
rawrules: List[JsonDict], enabled_map: Dict[str, bool] | |||
) -> FilteredPushRules: | |||
"""Take the DB rows returned from the DB and convert them into a full | |||
`FilteredPushRules` object. | |||
@@ -83,9 +80,7 @@ def _load_rules( | |||
push_rules = PushRules(ruleslist) | |||
filtered_rules = FilteredPushRules( | |||
push_rules, enabled_map, msc3772_enabled=experimental_config.msc3772_enabled | |||
) | |||
filtered_rules = FilteredPushRules(push_rules, enabled_map) | |||
return filtered_rules | |||
@@ -165,7 +160,7 @@ class PushRulesWorkerStore( | |||
enabled_map = await self.get_push_rules_enabled_for_user(user_id) | |||
return _load_rules(rows, enabled_map, self.hs.config.experimental) | |||
return _load_rules(rows, enabled_map) | |||
async def get_push_rules_enabled_for_user(self, user_id: str) -> Dict[str, bool]: | |||
results = await self.db_pool.simple_select_list( | |||
@@ -224,9 +219,7 @@ class PushRulesWorkerStore( | |||
results: Dict[str, FilteredPushRules] = {} | |||
for user_id, rules in raw_rules.items(): | |||
results[user_id] = _load_rules( | |||
rules, enabled_map_by_user.get(user_id, {}), self.hs.config.experimental | |||
) | |||
results[user_id] = _load_rules(rules, enabled_map_by_user.get(user_id, {})) | |||
return results | |||
@@ -776,59 +776,6 @@ class RelationsWorkerStore(SQLBaseStore): | |||
"get_if_user_has_annotated_event", _get_if_user_has_annotated_event | |||
) | |||
@cached(iterable=True) | |||
async def get_mutual_event_relations_for_rel_type( | |||
self, event_id: str, relation_type: str | |||
) -> Set[Tuple[str, str]]: | |||
raise NotImplementedError() | |||
@cachedList( | |||
cached_method_name="get_mutual_event_relations_for_rel_type", | |||
list_name="relation_types", | |||
) | |||
async def get_mutual_event_relations( | |||
self, event_id: str, relation_types: Collection[str] | |||
) -> 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. | |||
relation_types: The relation types to check for mutual relations. | |||
Returns: | |||
A dictionary of relation type to: | |||
A set of tuples of: | |||
The sender | |||
The event type | |||
""" | |||
rel_type_sql, rel_type_args = make_in_list_sql_clause( | |||
self.database_engine, "relation_type", relation_types | |||
) | |||
sql = f""" | |||
SELECT DISTINCT relation_type, sender, type FROM event_relations | |||
INNER JOIN events USING (event_id) | |||
WHERE relates_to_id = ? AND {rel_type_sql} | |||
""" | |||
def _get_event_relations( | |||
txn: LoggingTransaction, | |||
) -> Dict[str, Set[Tuple[str, str]]]: | |||
txn.execute(sql, [event_id] + rel_type_args) | |||
result: Dict[str, Set[Tuple[str, str]]] = { | |||
rel_type: set() for rel_type in relation_types | |||
} | |||
for rel_type, sender, type in txn.fetchall(): | |||
result[rel_type].add((sender, type)) | |||
return result | |||
return await self.db_pool.runInteraction( | |||
"get_event_relations", _get_event_relations | |||
) | |||
@cached() | |||
async def get_thread_id(self, event_id: str) -> Optional[str]: | |||
""" | |||
@@ -12,7 +12,7 @@ | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
from typing import Dict, Optional, Set, Tuple, Union | |||
from typing import Dict, Optional, Union | |||
import frozendict | |||
@@ -38,12 +38,7 @@ from tests.test_utils.event_injection import create_event, inject_member_event | |||
class PushRuleEvaluatorTestCase(unittest.TestCase): | |||
def _get_evaluator( | |||
self, | |||
content: JsonDict, | |||
relations: Optional[Dict[str, Set[Tuple[str, str]]]] = None, | |||
relations_match_enabled: bool = False, | |||
) -> PushRuleEvaluator: | |||
def _get_evaluator(self, content: JsonDict) -> PushRuleEvaluator: | |||
event = FrozenEvent( | |||
{ | |||
"event_id": "$event_id", | |||
@@ -63,8 +58,6 @@ class PushRuleEvaluatorTestCase(unittest.TestCase): | |||
room_member_count, | |||
sender_power_level, | |||
power_levels.get("notifications", {}), | |||
relations or {}, | |||
relations_match_enabled, | |||
) | |||
def test_display_name(self) -> None: | |||
@@ -299,71 +292,6 @@ class PushRuleEvaluatorTestCase(unittest.TestCase): | |||
{"sound": "default", "highlight": True}, | |||
) | |||
def test_relation_match(self) -> None: | |||
"""Test the relation_match push rule kind.""" | |||
# Check if the experimental feature is disabled. | |||
evaluator = self._get_evaluator( | |||
{}, {"m.annotation": {("@user:test", "m.reaction")}} | |||
) | |||
# A push rule evaluator with the experimental rule enabled. | |||
evaluator = self._get_evaluator( | |||
{}, {"m.annotation": {("@user:test", "m.reaction")}}, True | |||
) | |||
# Check just relation type. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
} | |||
self.assertTrue(evaluator.matches(condition, "@user:test", "foo")) | |||
# Check relation type and sender. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
"sender": "@user:test", | |||
} | |||
self.assertTrue(evaluator.matches(condition, "@user:test", "foo")) | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
"sender": "@other:test", | |||
} | |||
self.assertFalse(evaluator.matches(condition, "@user:test", "foo")) | |||
# Check relation type and event type. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
"type": "m.reaction", | |||
} | |||
self.assertTrue(evaluator.matches(condition, "@user:test", "foo")) | |||
# Check just sender, this fails since rel_type is required. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"sender": "@user:test", | |||
} | |||
self.assertFalse(evaluator.matches(condition, "@user:test", "foo")) | |||
# Check sender glob. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
"sender": "@*:test", | |||
} | |||
self.assertTrue(evaluator.matches(condition, "@user:test", "foo")) | |||
# Check event type glob. | |||
condition = { | |||
"kind": "org.matrix.msc3772.relation_match", | |||
"rel_type": "m.annotation", | |||
"event_type": "*.reaction", | |||
} | |||
self.assertTrue(evaluator.matches(condition, "@user:test", "foo")) | |||
class TestBulkPushRuleEvaluator(unittest.HomeserverTestCase): | |||
"""Tests for the bulk push rule evaluator""" | |||