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

Polls: count undecryptable poll relations #3163

Merged
merged 3 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 42 additions & 1 deletion spec/unit/models/poll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe("Poll", () => {
});

it("filters relations for relevent response events", async () => {
const replyEvent = new MatrixEvent({ type: "m.room.message" });
const replyEvent = makeRelatedEvent({ type: "m.room.message" });
const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! });
const unstableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable });

Expand Down Expand Up @@ -188,6 +188,47 @@ describe("Poll", () => {
});
});

describe("undecryptable relations", () => {
it("counts undecryptable relation events when getting responses", async () => {
const replyEvent = makeRelatedEvent({ type: "m.room.message" });
const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! });
const undecryptableEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable });
jest.spyOn(undecryptableEvent, "isDecryptionFailure").mockReturnValue(true);

mockClient.relations.mockResolvedValue({
events: [replyEvent, stableResponseEvent, undecryptableEvent],
});
const poll = new Poll(basePollStartEvent, mockClient, room);
jest.spyOn(poll, "emit");
await poll.getResponses();
expect(poll.undecryptableRelationsCount).toBe(1);
expect(poll.emit).toHaveBeenCalledWith(PollEvent.UndecryptableRelations, 1);
});

it("adds to undercryptable event count when new relation is undecryptable", async () => {
const replyEvent = makeRelatedEvent({ type: "m.room.message" });
const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! });
const undecryptableEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable });
const undecryptableEvent2 = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable });
jest.spyOn(undecryptableEvent, "isDecryptionFailure").mockReturnValue(true);
jest.spyOn(undecryptableEvent2, "isDecryptionFailure").mockReturnValue(true);

mockClient.relations.mockResolvedValue({
events: [replyEvent, stableResponseEvent, undecryptableEvent],
});
const poll = new Poll(basePollStartEvent, mockClient, room);
jest.spyOn(poll, "emit");
await poll.getResponses();
expect(poll.undecryptableRelationsCount).toBe(1);

await poll.onNewRelation(undecryptableEvent2);

expect(poll.undecryptableRelationsCount).toBe(2);

expect(poll.emit).toHaveBeenCalledWith(PollEvent.UndecryptableRelations, 2);
});
});

describe("with poll end event", () => {
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: "@bob@server.org" });
const unstablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.unstable!, sender: "@bob@server.org" });
Expand Down
33 changes: 32 additions & 1 deletion src/models/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export enum PollEvent {
Update = "Poll.update",
Responses = "Poll.Responses",
Destroy = "Poll.Destroy",
UndecryptableRelations = "Poll.UndecryptableRelations",
}

export type PollEventHandlerMap = {
[PollEvent.Update]: (event: MatrixEvent, poll: Poll) => void;
[PollEvent.Destroy]: (pollIdentifier: string) => void;
[PollEvent.End]: () => void;
[PollEvent.Responses]: (responses: Relations) => void;
[PollEvent.UndecryptableRelations]: (count: number) => void;
};

const filterResponseRelations = (
Expand All @@ -45,7 +47,6 @@ const filterResponseRelations = (
} => {
const responseEvents = relationEvents.filter((event) => {
if (event.isDecryptionFailure()) {
// @TODO(kerrya) PSG-1023 track and return these
return;
}
return (
Expand All @@ -66,6 +67,11 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
private relationsNextBatch: string | undefined;
private responses: null | Relations = null;
private endEvent: MatrixEvent | undefined;
/**
* Keep track of undecryptable relations
* As incomplete result sets affect poll results
*/
private undecryptableRelationEventIds = new Set<string>();

public constructor(public readonly rootEvent: MatrixEvent, private matrixClient: MatrixClient, private room: Room) {
super();
Expand All @@ -80,6 +86,10 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
return this.rootEvent.getId()!;
}

public get endEventId(): string | undefined {
return this.endEvent?.getId();
}

public get isEnded(): boolean {
return !!this.endEvent;
}
Expand All @@ -88,6 +98,10 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
return this._isFetchingResponses;
}

public get undecryptableRelationsCount(): number {
return this.undecryptableRelationEventIds.size;
}

public async getResponses(): Promise<Relations> {
// if we have already fetched some responses
// just return them
Expand Down Expand Up @@ -124,10 +138,13 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
const pollEndTimestamp = this.endEvent?.getTs() || Number.MAX_SAFE_INTEGER;
const { responseEvents } = filterResponseRelations([event], pollEndTimestamp);

this.countUndecryptableEvents([event]);

if (responseEvents.length) {
responseEvents.forEach((event) => {
this.responses!.addEvent(event);
});

this.emit(PollEvent.Responses, this.responses);
}
}
Expand Down Expand Up @@ -173,6 +190,7 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P

this.relationsNextBatch = allRelations.nextBatch ?? undefined;
this.responses = responses;
this.countUndecryptableEvents(allRelations.events);

// while there are more pages of relations
// fetch them
Expand Down Expand Up @@ -209,6 +227,19 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
this.emit(PollEvent.Responses, this.responses);
}

private countUndecryptableEvents = (events: MatrixEvent[]): void => {
const undecryptableEventIds = events
.filter((event) => event.isDecryptionFailure())
.map((event) => event.getId()!);

const previousCount = this.undecryptableRelationsCount;
this.undecryptableRelationEventIds = new Set([...this.undecryptableRelationEventIds, ...undecryptableEventIds]);

if (this.undecryptableRelationsCount !== previousCount) {
this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount);
}
};

private validateEndEvent(endEvent?: MatrixEvent): boolean {
if (!endEvent) {
return false;
Expand Down