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

Loading threads with server-side assistance #2735

Merged
merged 33 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e32b9d4
Fix bug where undefined vs null in pagination tokens wasn't correctly…
justjanne Oct 11, 2022
979ac99
Fix bug where thread list results were sorted incorrectly
justjanne Oct 11, 2022
317272a
Allow removing the relationship of an event to a thread
justjanne Oct 11, 2022
f6f8557
Implement feature detection for new threads MSCs and specs
justjanne Oct 21, 2022
f8b5063
Prefix dir parameter for threads pagination if necessary
justjanne Oct 21, 2022
5972e3c
Make threads conform to the same timeline APIs as any other timeline
justjanne Oct 12, 2022
6bf88b4
Extract thread timeline loading out of thread class
justjanne Oct 12, 2022
7459f49
Make typescript strict lint happier
justjanne Oct 21, 2022
4f79986
fix thread roots not being updated correctly
justjanne Oct 13, 2022
4a51e33
fix jumping to events by link
justjanne Oct 21, 2022
7fee96b
implement new thread timeline loading
justjanne Oct 21, 2022
a2e5088
reduce amount of failing tests
justjanne Oct 21, 2022
83c4724
Merge remote-tracking branch 'origin/develop' into justjanne/poc/thre…
justjanne Oct 25, 2022
f2dd7f9
Merge branch 'develop' into justjanne/poc/thread-server-loading
Oct 25, 2022
3ed5c17
Fix fetchRoomEvent incorrect return type
germain-gg Oct 26, 2022
d451f82
adapt tests for better compatibility
justjanne Oct 26, 2022
e445557
adapt tests for better compatibility
justjanne Oct 26, 2022
ab2c46d
Merge branch 'develop' into justjanne/poc/thread-server-loading
Oct 26, 2022
0961db6
fix strict null checks
germain-gg Oct 26, 2022
68bcb08
adapt tests for better compatibility
justjanne Oct 26, 2022
0041a5b
Disable three tests that have not been adapted to the changed threads…
justjanne Oct 27, 2022
70fab0e
fix eslint issues
justjanne Oct 27, 2022
e14aa76
fix eslint issues
justjanne Oct 27, 2022
145b9da
Merge branch 'develop' into justjanne/poc/thread-server-loading
justjanne Oct 27, 2022
a96cc00
reenabling test
justjanne Oct 27, 2022
7d189e7
increase test coverage
justjanne Oct 27, 2022
98c46c0
make eslint happy again
justjanne Oct 27, 2022
d345c7d
make ts-strict happy again
justjanne Oct 27, 2022
d90782e
adapt test to changed functionality
justjanne Oct 27, 2022
1565bf1
adapt test to changed functionality
justjanne Oct 27, 2022
66586a4
make ts-strict happy again
justjanne Oct 27, 2022
d26c071
further increase test coverage
justjanne Oct 28, 2022
dda885c
further increase test coverage
justjanne Oct 28, 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
222 changes: 144 additions & 78 deletions spec/integ/matrix-client-event-timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,14 @@ describe("MatrixClient event timelines", function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
Thread.setServerSideSupport(FeatureSupport.None);
Thread.setServerSideListSupport(FeatureSupport.None);
Thread.setServerSideFwdPaginationSupport(FeatureSupport.None);
});

async function flushHttp<T>(promise: Promise<T>): Promise<T> {
return Promise.all([promise, httpBackend.flushAllExpected()]).then(([result]) => result);
}

describe("getEventTimeline", function() {
it("should create a new timeline for new events", function() {
const room = client.getRoom(roomId)!;
Expand Down Expand Up @@ -595,31 +601,51 @@ describe("MatrixClient event timelines", function() {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);
client.stopClient(); // we don't need the client to be syncing at this time
await client.stopClient(); // we don't need the client to be syncing at this time
const room = client.getRoom(roomId)!;
const thread = room.createThread(THREAD_ROOT.event_id!, undefined, [], false);
const timelineSet = thread.timelineSet;

httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_REPLY.event_id!))
httpBackend.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id!))
.respond(200, function() {
return THREAD_ROOT;
});

httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id!) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + "?dir=b&limit=1")
.respond(200, function() {
return {
start: "start_token0",
events_before: [],
event: THREAD_REPLY,
events_after: [],
end: "end_token0",
state: [],
original_event: THREAD_ROOT,
chunk: [THREAD_REPLY],
// no next batch as this is the oldest end of the timeline
};
});

const thread = room.createThread(THREAD_ROOT.event_id!, undefined, [], false);
await httpBackend.flushAllExpected();
const timelineSet = thread.timelineSet;

const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id!);
const timeline = await timelinePromise;

expect(timeline!.getEvents().find(e => e.getId() === THREAD_ROOT.event_id!)).toBeTruthy();
expect(timeline!.getEvents().find(e => e.getId() === THREAD_REPLY.event_id!)).toBeTruthy();
});

it("should handle thread replies with server support by fetching a contiguous thread timeline", async () => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);
await client.stopClient(); // we don't need the client to be syncing at this time
const room = client.getRoom(roomId)!;

httpBackend.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id!))
.respond(200, function() {
return THREAD_ROOT;
});

httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id!) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + "?limit=20")
encodeURIComponent(THREAD_RELATION_TYPE.name) + "?dir=b&limit=1")
.respond(200, function() {
return {
original_event: THREAD_ROOT,
Expand All @@ -628,9 +654,11 @@ describe("MatrixClient event timelines", function() {
};
});

const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id!);
const thread = room.createThread(THREAD_ROOT.event_id!, undefined, [], false);
await httpBackend.flushAllExpected();
const timelineSet = thread.timelineSet;

const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id!);
const timeline = await timelinePromise;

expect(timeline!.getEvents().find(e => e.getId() === THREAD_ROOT.event_id!)).toBeTruthy();
Expand Down Expand Up @@ -1025,10 +1053,6 @@ describe("MatrixClient event timelines", function() {
});

describe("paginateEventTimeline for thread list timeline", function() {
async function flushHttp<T>(promise: Promise<T>): Promise<T> {
return Promise.all([promise, httpBackend.flushAllExpected()]).then(([result]) => result);
}

const RANDOM_TOKEN = "7280349c7bee430f91defe2a38a0a08c";

function respondToFilter(): ExpectedHttpRequest {
Expand All @@ -1050,7 +1074,7 @@ describe("MatrixClient event timelines", function() {
next_batch: RANDOM_TOKEN as string | null,
},
): ExpectedHttpRequest {
const request = httpBackend.when("GET", encodeUri("/_matrix/client/r0/rooms/$roomId/threads", {
const request = httpBackend.when("GET", encodeUri("/_matrix/client/v1/rooms/$roomId/threads", {
$roomId: roomId,
}));
request.respond(200, response);
Expand Down Expand Up @@ -1089,8 +1113,9 @@ describe("MatrixClient event timelines", function() {
beforeEach(() => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);
Thread.setServerSideSupport(FeatureSupport.Stable);
Thread.setServerSideListSupport(FeatureSupport.Stable);
Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable);
});

async function testPagination(timelineSet: EventTimelineSet, direction: Direction) {
Expand All @@ -1111,7 +1136,7 @@ describe("MatrixClient event timelines", function() {

it("should allow you to paginate all threads backwards", async function() {
const room = client.getRoom(roomId);
const timelineSets = await (room?.createThreadsTimelineSets());
const timelineSets = await room!.createThreadsTimelineSets();
expect(timelineSets).not.toBeNull();
const [allThreads, myThreads] = timelineSets!;
await testPagination(allThreads, Direction.Backward);
Expand All @@ -1120,7 +1145,7 @@ describe("MatrixClient event timelines", function() {

it("should allow you to paginate all threads forwards", async function() {
const room = client.getRoom(roomId);
const timelineSets = await (room?.createThreadsTimelineSets());
const timelineSets = await room!.createThreadsTimelineSets();
expect(timelineSets).not.toBeNull();
const [allThreads, myThreads] = timelineSets!;

Expand All @@ -1130,7 +1155,7 @@ describe("MatrixClient event timelines", function() {

it("should allow fetching all threads", async function() {
const room = client.getRoom(roomId)!;
const timelineSets = await room.createThreadsTimelineSets();
const timelineSets = await room!.createThreadsTimelineSets();
expect(timelineSets).not.toBeNull();
respondToThreads();
respondToThreads();
Expand Down Expand Up @@ -1418,74 +1443,115 @@ describe("MatrixClient event timelines", function() {
});
});

it("should re-insert room IDs for bundled thread relation events", async () => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);

httpBackend.when("GET", "/sync").respond(200, {
next_batch: "s_5_4",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
SYNC_THREAD_ROOT,
],
prev_batch: "f_1_1",
describe("should re-insert room IDs for bundled thread relation events", () => {
async function doTest() {
httpBackend.when("GET", "/sync").respond(200, {
next_batch: "s_5_4",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
SYNC_THREAD_ROOT,
],
prev_batch: "f_1_1",
},
},
},
},
},
});
await Promise.all([httpBackend.flushAllExpected(), utils.syncPromise(client)]);
});
await Promise.all([httpBackend.flushAllExpected(), utils.syncPromise(client)]);

const room = client.getRoom(roomId)!;
const thread = room.getThread(THREAD_ROOT.event_id!)!;
const timelineSet = thread.timelineSet;
const room = client.getRoom(roomId)!;
const thread = room.getThread(THREAD_ROOT.event_id!)!;
const timelineSet = thread.timelineSet;

httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_ROOT.event_id!))
.respond(200, {
start: "start_token",
events_before: [],
event: THREAD_ROOT,
events_after: [],
state: [],
end: "end_token",
});
httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id!) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + "?limit=20")
.respond(200, function() {
return {
original_event: THREAD_ROOT,
chunk: [THREAD_REPLY],
// no next batch as this is the oldest end of the timeline
};
});
await Promise.all([
client.getEventTimeline(timelineSet, THREAD_ROOT.event_id!),
httpBackend.flushAllExpected(),
]);
const buildParams = (direction: Direction, token: string): string => {
if (Thread.hasServerSideFwdPaginationSupport === FeatureSupport.Experimental) {
return `?from=${token}&org.matrix.msc3715.dir=${direction}`;
} else {
return `?dir=${direction}&from=${token}`;
}
};

httpBackend.when("GET", "/sync").respond(200, {
next_batch: "s_5_5",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
SYNC_THREAD_REPLY,
],
prev_batch: "f_1_2",
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_ROOT.event_id!))
.respond(200, {
start: "start_token",
events_before: [],
event: THREAD_ROOT,
events_after: [],
state: [],
end: "end_token",
});
httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id!) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + buildParams(Direction.Backward, "start_token"))
.respond(200, function() {
return {
original_event: THREAD_ROOT,
chunk: [],
};
});
httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id!) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + buildParams(Direction.Forward, "end_token"))
.respond(200, function() {
return {
original_event: THREAD_ROOT,
chunk: [THREAD_REPLY],
};
});
const timeline = await flushHttp(client.getEventTimeline(timelineSet, THREAD_ROOT.event_id!));

httpBackend.when("GET", "/sync").respond(200, {
next_batch: "s_5_5",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
SYNC_THREAD_REPLY,
],
prev_batch: "f_1_2",
},
},
},
},
},
});

await Promise.all([httpBackend.flushAllExpected(), utils.syncPromise(client)]);

expect(timeline!.getEvents()[1]!.event).toEqual(THREAD_REPLY);
}

it("in stable mode", async () => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Stable);
Thread.setServerSideListSupport(FeatureSupport.Stable);
Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable);

return doTest();
});

await Promise.all([httpBackend.flushAllExpected(), utils.syncPromise(client)]);
it("in backwards compatible unstable mode", async () => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);
Thread.setServerSideListSupport(FeatureSupport.Experimental);
Thread.setServerSideFwdPaginationSupport(FeatureSupport.Experimental);

expect(thread.liveTimeline.getEvents()[1].event).toEqual(THREAD_REPLY);
return doTest();
});

it("in backwards compatible mode", async () => {
// @ts-ignore
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(FeatureSupport.Experimental);
Thread.setServerSideListSupport(FeatureSupport.None);
Thread.setServerSideFwdPaginationSupport(FeatureSupport.None);

return doTest();
});
});
});
8 changes: 4 additions & 4 deletions spec/integ/matrix-client-relations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe("MatrixClient relations", () => {

await httpBackend!.flushAllExpected();

expect(await response).toEqual({ "events": [], "nextBatch": "NEXT" });
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
});

it("should read related events with relation type", async () => {
Expand All @@ -72,7 +72,7 @@ describe("MatrixClient relations", () => {

await httpBackend!.flushAllExpected();

expect(await response).toEqual({ "events": [], "nextBatch": "NEXT" });
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
});

it("should read related events with relation type and event type", async () => {
Expand All @@ -87,7 +87,7 @@ describe("MatrixClient relations", () => {

await httpBackend!.flushAllExpected();

expect(await response).toEqual({ "events": [], "nextBatch": "NEXT" });
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
});

it("should read related events with custom options", async () => {
Expand All @@ -107,7 +107,7 @@ describe("MatrixClient relations", () => {

await httpBackend!.flushAllExpected();

expect(await response).toEqual({ "events": [], "nextBatch": "NEXT" });
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
});

it('should use default direction in the fetchRelations endpoint', async () => {
Expand Down
Loading