Skip to content

Commit 97559da

Browse files
committed
feat(room): make load_event_with_relations also load relations when falling back to the network
Signed-off-by: Johannes Marbach <[email protected]>
1 parent acc6626 commit 97559da

File tree

2 files changed

+236
-14
lines changed

2 files changed

+236
-14
lines changed

crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
use std::{fmt::Formatter, sync::Arc};
1616

1717
use futures_util::{StreamExt, stream};
18-
use matrix_sdk::{BoxFuture, Room, SendOutsideWasm, SyncOutsideWasm, config::RequestConfig};
18+
use matrix_sdk::{
19+
BoxFuture, Room, SendOutsideWasm, SyncOutsideWasm,
20+
config::RequestConfig,
21+
room::{IncludeRelations, RelationsOptions},
22+
};
1923
use matrix_sdk_base::deserialized_responses::TimelineEvent;
2024
use ruma::{EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, events::relation::RelationType};
2125
use thiserror::Error;
@@ -180,7 +184,27 @@ impl PinnedEventsRoom for Room {
180184
}
181185

182186
debug!("Loading pinned event {event_id} from HS");
183-
self.event(event_id, request_config).await.map(|e| (e, Vec::new()))
187+
let event = self.event(event_id, request_config).await?;
188+
189+
debug!("Loading relations of pinned event {event_id} from HS");
190+
let mut related_events = Vec::new();
191+
let mut opts = RelationsOptions {
192+
include_relations: IncludeRelations::AllRelations,
193+
recurse: true,
194+
..Default::default()
195+
};
196+
197+
loop {
198+
let relations = self.relations(event_id.to_owned(), opts.clone()).await?;
199+
related_events.extend(relations.chunk);
200+
if let Some(next_from) = relations.next_batch_token {
201+
opts.from = Some(next_from);
202+
} else {
203+
break;
204+
}
205+
}
206+
207+
Ok((event, related_events))
184208
})
185209
}
186210

crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs

Lines changed: 210 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use matrix_sdk::{
88
config::SyncSettings,
99
test_utils::{
1010
logged_in_client_with_server,
11-
mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
11+
mocks::{MatrixMockServer, RoomMessagesResponseTemplate, RoomRelationsResponseTemplate},
1212
},
1313
};
1414
use matrix_sdk_base::deserialized_responses::TimelineEvent;
@@ -22,7 +22,7 @@ use ruma::{
2222
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId, UserId, assign,
2323
event_id,
2424
events::{
25-
AnySyncTimelineEvent,
25+
AnySyncTimelineEvent, AnyTimelineEvent,
2626
room::{
2727
encrypted::{
2828
EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
@@ -52,15 +52,25 @@ async fn test_new_pinned_events_are_not_added_on_sync() {
5252
let room_id = room_id!("!test:localhost");
5353

5454
let f = EventFactory::new().room(room_id).sender(*BOB);
55+
let event_id = event_id!("$1");
5556
let event_1 = f
5657
.text_msg("in the end")
57-
.event_id(event_id!("$1"))
58+
.event_id(event_id)
5859
.server_ts(MilliSecondsSinceUnixEpoch::now())
5960
.into_raw_sync();
6061

61-
// Mock /event endpoint for a timeline event
62+
// Mock /event endpoint for event $1.
6263
mock_events_endpoint(&server, room_id, vec![event_1]).await;
6364

65+
// Mock /relations for event $1.
66+
server
67+
.mock_room_relations()
68+
.match_target_event(event_id.to_owned())
69+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
70+
.mock_once()
71+
.mount()
72+
.await;
73+
6474
// Load initial timeline items: a `m.room.pinned_events` with events $1 and $2
6575
// pinned
6676
let room = PinnedEventsSync::new(room_id)
@@ -103,27 +113,178 @@ async fn test_new_pinned_events_are_not_added_on_sync() {
103113
assert_pending!(timeline_stream);
104114
}
105115

116+
#[async_test]
117+
async fn test_pinned_event_with_reaction() {
118+
let server = MatrixMockServer::new().await;
119+
let client = server.client_builder().build().await;
120+
let room_id = room_id!("!test:localhost");
121+
122+
let f = EventFactory::new().room(room_id).sender(*BOB);
123+
let event_id = event_id!("$1");
124+
let event_1 = f
125+
.text_msg("in the end")
126+
.event_id(event_id)
127+
.server_ts(MilliSecondsSinceUnixEpoch::now())
128+
.into_raw_sync();
129+
130+
let reaction = f.reaction(event_id, "👀").into_raw_timeline();
131+
132+
// Mock /event endpoint for event $1.
133+
mock_events_endpoint(&server, room_id, vec![event_1]).await;
134+
135+
// Mock /relations for event $1.
136+
server
137+
.mock_room_relations()
138+
.match_target_event(event_id.to_owned())
139+
.ok(RoomRelationsResponseTemplate::default().events(vec![reaction]))
140+
.mock_once()
141+
.mount()
142+
.await;
143+
144+
// Load initial timeline items: a `m.room.pinned_events` with event $1 pinned
145+
let room = PinnedEventsSync::new(room_id)
146+
.with_pinned_event_ids(vec!["$1"])
147+
.mock_and_sync(&client, &server)
148+
.await
149+
.expect("Room should be synced");
150+
let timeline =
151+
TimelineBuilder::new(&room).with_focus(pinned_events_focus(100)).build().await.unwrap();
152+
153+
assert!(
154+
timeline.live_back_pagination_status().await.is_none(),
155+
"there should be no live back-pagination status for a pinned events timeline"
156+
);
157+
158+
// Load timeline items
159+
let (items, mut timeline_stream) = timeline.subscribe().await;
160+
161+
// Verify that the timeline contains the pinned event and its reaction.
162+
assert_eq!(items.len(), 1 + 1); // event item + a date divider
163+
assert!(items[0].is_date_divider());
164+
assert_eq!(items[1].as_event().unwrap().content().as_message().unwrap().body(), "in the end");
165+
let reactions = items[1]
166+
.as_event()
167+
.unwrap()
168+
.content()
169+
.reactions()
170+
.expect("pinned event should have reactions");
171+
assert_eq!(reactions.len(), 1);
172+
assert!(reactions.get("👀").is_some());
173+
assert_pending!(timeline_stream);
174+
}
175+
176+
#[async_test]
177+
async fn test_pinned_event_with_paginated_reactions() {
178+
let server = MatrixMockServer::new().await;
179+
let client = server.client_builder().build().await;
180+
let room_id = room_id!("!test:localhost");
181+
182+
let f = EventFactory::new().room(room_id).sender(*BOB);
183+
let event_id = event_id!("$1");
184+
let event_1 = f
185+
.text_msg("in the end")
186+
.event_id(event_id)
187+
.server_ts(MilliSecondsSinceUnixEpoch::now())
188+
.into_raw_sync();
189+
190+
let reaction_1 = f.reaction(event_id, "👀").into_raw_timeline();
191+
let reaction_2 = f.reaction(event_id, "🤔").into_raw_timeline();
192+
193+
// Mock /event endpoint for event $1.
194+
mock_events_endpoint(&server, room_id, vec![event_1]).await;
195+
196+
// Mock /relations for event $1, split across two pages.
197+
let token = "foo";
198+
server
199+
.mock_room_relations()
200+
.match_target_event(event_id.to_owned())
201+
.ok(RoomRelationsResponseTemplate::default().events(vec![reaction_1]).next_batch(token))
202+
.mock_once()
203+
.mount()
204+
.await;
205+
server
206+
.mock_room_relations()
207+
.match_target_event(event_id.to_owned())
208+
.match_from(token)
209+
.ok(RoomRelationsResponseTemplate::default().events(vec![reaction_2]))
210+
.mock_once()
211+
.mount()
212+
.await;
213+
214+
// Load initial timeline items: a `m.room.pinned_events` with event $1 pinned
215+
let room = PinnedEventsSync::new(room_id)
216+
.with_pinned_event_ids(vec!["$1"])
217+
.mock_and_sync(&client, &server)
218+
.await
219+
.expect("Room should be synced");
220+
let timeline =
221+
TimelineBuilder::new(&room).with_focus(pinned_events_focus(100)).build().await.unwrap();
222+
223+
assert!(
224+
timeline.live_back_pagination_status().await.is_none(),
225+
"there should be no live back-pagination status for a pinned events timeline"
226+
);
227+
228+
// Load timeline items
229+
let (items, mut timeline_stream) = timeline.subscribe().await;
230+
231+
// Verify that the timeline contains the pinned event and its reactions.
232+
assert_eq!(items.len(), 1 + 1); // event item + a date divider
233+
assert!(items[0].is_date_divider());
234+
assert_eq!(items[1].as_event().unwrap().content().as_message().unwrap().body(), "in the end");
235+
let reactions = items[1]
236+
.as_event()
237+
.unwrap()
238+
.content()
239+
.reactions()
240+
.expect("pinned event should have reactions");
241+
assert_eq!(reactions.len(), 2);
242+
assert!(reactions.get("👀").is_some());
243+
assert!(reactions.get("🤔").is_some());
244+
assert_pending!(timeline_stream);
245+
}
246+
106247
#[async_test]
107248
async fn test_new_pinned_event_ids_reload_the_timeline() {
108249
let server = MatrixMockServer::new().await;
109250
let client = server.client_builder().build().await;
110251
let room_id = room_id!("!test:localhost");
111252

112253
let f = EventFactory::new().room(room_id).sender(*BOB);
254+
let event_id_1 = event_id!("$1");
113255
let event_1 = f
114256
.text_msg("in the end")
115-
.event_id(event_id!("$1"))
257+
.event_id(event_id_1)
116258
.server_ts(MilliSecondsSinceUnixEpoch::now())
117259
.into_raw_sync();
260+
let event_id_2 = event_id!("$2");
118261
let event_2 = f
119262
.text_msg("it doesn't even matter")
120-
.event_id(event_id!("$2"))
263+
.event_id(event_id_2)
121264
.server_ts(MilliSecondsSinceUnixEpoch::now())
122265
.into_raw_sync();
123266

267+
// Mock /event endpoint for events $1 and $2.
268+
mock_events_endpoint(&server, room_id, vec![event_1.clone(), event_2.clone()]).await;
269+
270+
// Mock /relations endpoint for events $1 and $2.
271+
server
272+
.mock_room_relations()
273+
.match_target_event(event_id_1.to_owned())
274+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
275+
.mock_once()
276+
.mount()
277+
.await;
278+
server
279+
.mock_room_relations()
280+
.match_target_event(event_id_2.to_owned())
281+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
282+
.mock_once()
283+
.mount()
284+
.await;
285+
124286
// Load initial timeline items: 2 text messages and a `m.room.pinned_events`
125287
// with event $1 and $2 pinned
126-
mock_events_endpoint(&server, room_id, vec![event_1.clone(), event_2.clone()]).await;
127288
let room = PinnedEventsSync::new(room_id)
128289
.with_timeline_events(vec![event_2.clone()])
129290
.with_pinned_event_ids(vec!["$1"])
@@ -225,15 +386,25 @@ async fn test_cached_events_are_kept_for_different_room_instances() {
225386
event_cache.subscribe().unwrap();
226387

227388
let f = EventFactory::new().room(room_id).sender(*BOB);
389+
let event_id = event_id!("$1");
228390
let pinned_event = f
229391
.text_msg("in the end")
230-
.event_id(event_id!("$1"))
392+
.event_id(event_id)
231393
.server_ts(MilliSecondsSinceUnixEpoch::now())
232394
.into_raw_sync();
233395

234-
// Mock /event for some timeline events
396+
// Mock /event for the pinned event.
235397
mock_events_endpoint(&server, room_id, vec![pinned_event]).await;
236398

399+
// Mock /relations for the pinned event.
400+
server
401+
.mock_room_relations()
402+
.match_target_event(event_id.to_owned())
403+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
404+
.mock_once()
405+
.mount()
406+
.await;
407+
237408
// Load initial timeline items: a `m.room.pinned_events` with event $1 and $2
238409
// pinned
239410
let room = PinnedEventsSync::new(room_id)
@@ -427,6 +598,13 @@ async fn test_pinned_timeline_with_pinned_utd_on_sync_contains_it() {
427598
// Mock encrypted pinned event for which we don't have keys (an UTD)
428599
let utd_event = create_utd(room_id, &sender_id, event_id);
429600
mock_events_endpoint(&server, room_id, vec![utd_event]).await;
601+
server
602+
.mock_room_relations()
603+
.match_target_event(event_id.to_owned())
604+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
605+
.mock_once()
606+
.mount()
607+
.await;
430608

431609
let timeline =
432610
TimelineBuilder::new(&room).with_focus(pinned_events_focus(1)).build().await.unwrap();
@@ -445,15 +623,25 @@ async fn test_edited_events_are_reflected_in_sync() {
445623
let room_id = room_id!("!test:localhost");
446624

447625
let f = EventFactory::new().room(room_id).sender(*BOB);
626+
let event_id = event_id!("$1");
448627
let pinned_event = f
449628
.text_msg("in the end")
450-
.event_id(event_id!("$1"))
629+
.event_id(event_id)
451630
.server_ts(MilliSecondsSinceUnixEpoch::now())
452631
.into_raw_sync();
453632

454-
// Mock /event for some timeline events.
633+
// Mock /event for the pinned event.
455634
mock_events_endpoint(&server, room_id, vec![pinned_event]).await;
456635

636+
// Mock /relations for the pinned event.
637+
server
638+
.mock_room_relations()
639+
.match_target_event(event_id.to_owned())
640+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
641+
.mock_once()
642+
.mount()
643+
.await;
644+
457645
// Load initial timeline items: a text message and a `m.room.pinned_events` with
458646
// event $1.
459647
let room = PinnedEventsSync::new(room_id)
@@ -517,15 +705,25 @@ async fn test_redacted_events_are_reflected_in_sync() {
517705
let room_id = room_id!("!test:localhost");
518706

519707
let f = EventFactory::new().room(room_id).sender(*BOB);
708+
let event_id = event_id!("$1");
520709
let pinned_event = f
521710
.text_msg("in the end")
522711
.event_id(event_id!("$1"))
523712
.server_ts(MilliSecondsSinceUnixEpoch::now())
524713
.into_raw_sync();
525714

526-
// Mock /event for some timeline events
715+
// Mock /event for the pinned timeline event.
527716
mock_events_endpoint(&server, room_id, vec![pinned_event]).await;
528717

718+
// Mock /relations for pinned timeline event.
719+
server
720+
.mock_room_relations()
721+
.match_target_event(event_id.to_owned())
722+
.ok(RoomRelationsResponseTemplate::default().events(Vec::<Raw<AnyTimelineEvent>>::new()))
723+
.mock_once()
724+
.mount()
725+
.await;
726+
529727
// Load initial timeline items: a text message and a `m.room.pinned_events` with
530728
// event $1
531729
let room = PinnedEventsSync::new(room_id)

0 commit comments

Comments
 (0)