From c931cdfdf758e1fc79b10b92515c47771180f5fd Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Wed, 30 Nov 2022 14:35:29 +0100 Subject: [PATCH 1/4] Include pending events in thread summary and count again --- src/models/thread.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/models/thread.ts b/src/models/thread.ts index 8c9826c35ec..a130be670c7 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -16,10 +16,10 @@ limitations under the License. import { Optional } from "matrix-events-sdk"; -import { MatrixClient } from "../client"; +import { MatrixClient, PendingEventOrdering } from "../client"; import { TypedReEmitter } from "../ReEmitter"; import { RelationType } from "../@types/event"; -import { IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "./event"; +import { EventStatus, IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "./event"; import { EventTimeline } from "./event-timeline"; import { EventTimelineSet, EventTimelineSetHandlerMap } from './event-timeline-set'; import { NotificationCountType, Room, RoomEvent } from './room'; @@ -51,6 +51,7 @@ export type EventHandlerMap = { interface IThreadOpts { room: Room; client: MatrixClient; + pendingEventOrdering?: PendingEventOrdering; } export enum FeatureSupport { @@ -88,9 +89,12 @@ export class Thread extends ReadReceipt { private lastEvent: MatrixEvent | undefined; private replyCount = 0; + private lastPendingEvent: MatrixEvent | undefined; + private pendingReplyCount = 0; public readonly room: Room; public readonly client: MatrixClient; + private readonly pendingEventOrdering: PendingEventOrdering; public initialEventsFetched = !Thread.hasServerSideSupport; @@ -109,6 +113,7 @@ export class Thread extends ReadReceipt { this.room = opts.room; this.client = opts.client; + this.pendingEventOrdering = opts.pendingEventOrdering ?? PendingEventOrdering.Chronological; this.timelineSet = new EventTimelineSet(this.room, { timelineSupport: true, pendingEvents: true, @@ -300,6 +305,18 @@ export class Thread extends ReadReceipt { bundledRelationship = this.getRootEventBundledRelationship(); } + let pendingEvents: MatrixEvent[] = []; + if (this.pendingEventOrdering === PendingEventOrdering.Detached) { + pendingEvents = this.room.getPendingEvents().filter((ev) => { + const isNotSent = ev.status === EventStatus.NOT_SENT; + const belongsToTheThread = this.id === ev.threadRootId; + return isNotSent && belongsToTheThread; + }); + await Promise.all(pendingEvents.map(ev => this.processEvent(ev))); + } + this.lastPendingEvent = pendingEvents.length ? pendingEvents[pendingEvents.length - 1] : undefined; + this.pendingReplyCount = pendingEvents.length; + if (Thread.hasServerSideSupport && bundledRelationship) { this.replyCount = bundledRelationship.count; this._currentUserParticipated = !!bundledRelationship.current_user_participated; @@ -393,14 +410,14 @@ export class Thread extends ReadReceipt { * exclude annotations from that number */ public get length(): number { - return this.replyCount; + return this.replyCount + this.pendingReplyCount; } /** * A getter for the last event added to the thread, if known. */ public get replyToEvent(): Optional { - return this.lastEvent ?? this.lastReply(); + return this.lastPendingEvent ?? this.lastEvent ?? this.lastReply(); } public get events(): MatrixEvent[] { From f4d6278ada89895dc71ca11daf32837b8cfd7ff3 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Thu, 1 Dec 2022 17:59:46 +0100 Subject: [PATCH 2/4] Pass through pending event status --- src/models/room.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/room.ts b/src/models/room.ts index de3d18733de..7c01fca5ca7 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -2017,6 +2017,7 @@ export class Room extends ReadReceipt { const thread = new Thread(threadId, rootEvent, { room: this, client: this.client, + pendingEventOrdering: this.opts.pendingEventOrdering, }); // This is necessary to be able to jump to events in threads: From 1ff850629a3967b9ab00ad7edfa4a5cffee10b16 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Thu, 1 Dec 2022 18:00:11 +0100 Subject: [PATCH 3/4] Correctly include all non-sent thread-relevant pending messages --- src/models/thread.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/models/thread.ts b/src/models/thread.ts index a130be670c7..d641ba8ec3b 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -305,14 +305,15 @@ export class Thread extends ReadReceipt { bundledRelationship = this.getRootEventBundledRelationship(); } - let pendingEvents: MatrixEvent[] = []; + let pendingEvents: MatrixEvent[]; if (this.pendingEventOrdering === PendingEventOrdering.Detached) { - pendingEvents = this.room.getPendingEvents().filter((ev) => { - const isNotSent = ev.status === EventStatus.NOT_SENT; - const belongsToTheThread = this.id === ev.threadRootId; - return isNotSent && belongsToTheThread; - }); + pendingEvents = this.room.getPendingEvents() + .filter(ev => ev.isRelation(THREAD_RELATION_TYPE.name) && this.id === ev.threadRootId); await Promise.all(pendingEvents.map(ev => this.processEvent(ev))); + } else { + pendingEvents = this.events + .filter(ev => ev.isRelation(THREAD_RELATION_TYPE.name)) + .filter(ev => ev.status !== EventStatus.SENT && ev.status !== EventStatus.CANCELLED); } this.lastPendingEvent = pendingEvents.length ? pendingEvents[pendingEvents.length - 1] : undefined; this.pendingReplyCount = pendingEvents.length; From e70f3a019cff18132e8c0617de154870a6989268 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Thu, 1 Dec 2022 18:01:08 +0100 Subject: [PATCH 4/4] Write new threads --- spec/unit/models/thread.spec.ts | 50 +++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index 37e77954795..1ffb466370f 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient } from "../../../src/client"; +import { MatrixClient, PendingEventOrdering } from "../../../src/client"; import { Room } from "../../../src/models/room"; -import { Thread } from "../../../src/models/thread"; +import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/thread"; import { mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; +import { emitPromise, mkMessage } from "../../test-utils/test-utils"; +import { EventStatus } from "../../../src"; describe('Thread', () => { describe("constructor", () => { @@ -30,6 +32,50 @@ describe('Thread', () => { }); }); + it("includes pending events in replyCount", async () => { + const myUserId = "@bob:example.org"; + const testClient = new TestClient( + myUserId, + "DEVICE", + "ACCESS_TOKEN", + undefined, + { timelineSupport: false }, + ); + const client = testClient.client; + const room = new Room("123", client, myUserId, { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + + jest.spyOn(client, "getRoom").mockReturnValue(room); + + const { thread } = mkThread({ + room, + client, + authorId: myUserId, + participantUserIds: ["@alice:example.org"], + length: 3, + }); + await emitPromise(thread, ThreadEvent.Update); + expect(thread.length).toBe(2); + + const event = mkMessage({ + room: room.roomId, + user: myUserId, + msg: "thread reply", + relatesTo: { + rel_type: THREAD_RELATION_TYPE.name, + event_id: thread.id, + }, + event: true, + }); + await thread.processEvent(event); + event.setStatus(EventStatus.SENDING); + room.addPendingEvent(event, "txn01"); + + await emitPromise(thread, ThreadEvent.Update); + expect(thread.length).toBe(3); + }); + describe("hasUserReadEvent", () => { const myUserId = "@bob:example.org"; let client: MatrixClient;