From 9e18d8520b1d0811ca87d48bcc28bfeb68c6a4a5 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 14 Feb 2023 11:18:32 +1300 Subject: [PATCH 01/11] use timeline pagination --- .../views/dialogs/polls/PollHistoryDialog.tsx | 11 ++- .../views/dialogs/polls/fetchPastPolls.ts | 96 +++++++++++++++++++ .../views/dialogs/polls/usePollHistory.ts | 4 +- 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/components/views/dialogs/polls/fetchPastPolls.ts diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index e5525fbbaf7..432a852d608 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -24,6 +24,7 @@ import { IDialogProps } from "../IDialogProps"; import { PollHistoryList } from "./PollHistoryList"; import { PollHistoryFilter } from "./types"; import { usePolls } from "./usePollHistory"; +import { useFetchPastPolls } from "./fetchPastPolls"; type PollHistoryDialogProps = Pick & { roomId: string; @@ -51,10 +52,18 @@ export const PollHistoryDialog: React.FC = ({ roomId, ma setPollStartEvents(filterAndSortPolls(polls, filter)); }, [filter, polls]); + const room = matrixClient.getRoom(roomId)!; + const { isLoading } = useFetchPastPolls(room, matrixClient); + return (
- +
); diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts new file mode 100644 index 00000000000..ed2557d79e5 --- /dev/null +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -0,0 +1,96 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { Direction, EventTimeline, EventTimelineSet, Room } from "matrix-js-sdk/src/matrix"; +import { Filter, IFilterDefinition } from "matrix-js-sdk/src/filter"; +import { useEffect, useRef, useState } from "react"; + +const pagePolls = async (timelineSet: EventTimelineSet, matrixClient: MatrixClient, endOfHistoryPeriodTimestamp: number): Promise => { + const liveTimeline = timelineSet.getLiveTimeline(); + const events = liveTimeline.getEvents(); + const oldestEventTimestamp = events[0]?.getTs() || Date.now(); + const hasMorePages = !!liveTimeline.getPaginationToken(EventTimeline.BACKWARDS); + console.log('hhh token', liveTimeline.getPaginationToken(EventTimeline.BACKWARDS)); + console.log('hhhhh', { hasMorePages, oldestEventTimestamp, endOfHistoryPeriodTimestamp, oldestEventD: new Date(oldestEventTimestamp).toISOString() }) + if (!hasMorePages || oldestEventTimestamp <= endOfHistoryPeriodTimestamp) { + return; + } + + await matrixClient.paginateEventTimeline(liveTimeline, { + backwards: true + }); + + return pagePolls(timelineSet, matrixClient, endOfHistoryPeriodTimestamp); +} + +const ONE_DAY_MS = 60000 * 60 * 24; +const useFilteredRoomHistory = (timelineSet: EventTimelineSet | null, matrixClient: MatrixClient, historyPeriodDays: number): { isLoading: boolean } => { + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + console.log('hhh useFilteredRoomHistory', {timelineSet}) + if (!timelineSet) { + return; + } + // @TODO(kerrya) maybe set this to start of current day - days + const endOfHistoryPeriodTimestamp = Date.now() - (ONE_DAY_MS * historyPeriodDays); + + const doFetchHistory = async (): Promise => { + setIsLoading(true); + try { + await pagePolls(timelineSet, matrixClient, endOfHistoryPeriodTimestamp); + } finally { + setIsLoading(false); + } + } + doFetchHistory(); + }, [timelineSet]); + + return { isLoading }; +} + +export const useFetchPastPolls = (room: Room, matrixClient: MatrixClient, historyPeriodDays = 30): { isLoading: boolean; } => { + console.log('hhh', { room }); + + const filterDefinition: IFilterDefinition = { + room: { + timeline: { + types: [M_POLL_START.name, M_POLL_START.altName] + }, + }, + }; + + const [timelineSet, setTimelineSet] = useState(null); + + useEffect(() => { + const filter = new Filter(matrixClient.getSafeUserId()); + filter.setDefinition(filterDefinition); + const getFilteredTimelineSet = async () => { + const filterId = await matrixClient.getOrCreateFilter(`POLL_HISTORY_FILTER_${room.roomId}}`, filter); + filter.filterId = filterId; + const timelineSet = room.getOrCreateFilteredTimelineSet(filter); + setTimelineSet(timelineSet); + } + + getFilteredTimelineSet(); + }, [room]); + + const { isLoading } = useFilteredRoomHistory(timelineSet, matrixClient, historyPeriodDays); + + return { isLoading }; +} \ No newline at end of file diff --git a/src/components/views/dialogs/polls/usePollHistory.ts b/src/components/views/dialogs/polls/usePollHistory.ts index 1da2b4ee1de..fa5bf220189 100644 --- a/src/components/views/dialogs/polls/usePollHistory.ts +++ b/src/components/views/dialogs/polls/usePollHistory.ts @@ -18,6 +18,7 @@ import { Poll, PollEvent } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { useEventEmitterState } from "../../../../hooks/useEventEmitter"; +import { useEffect } from "react"; /** * Get poll instances from a room @@ -37,7 +38,8 @@ export const usePolls = ( throw new Error("Cannot find room"); } - const polls = useEventEmitterState(room, PollEvent.New, () => room.polls); + // copy room.polls map so changes can be detected + const polls = useEventEmitterState(room, PollEvent.New, () => new Map(room.polls)); // @TODO(kerrya) watch polls for end events, trigger refiltering From f18e37c71539e8af31309aee7990b5f9ede15b99 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 14 Feb 2023 18:09:50 +1300 Subject: [PATCH 02/11] fetch last 30 days of poll history --- .../views/dialogs/polls/_PollHistoryList.pcss | 11 + .../views/dialogs/polls/PollHistoryDialog.tsx | 12 +- .../views/dialogs/polls/PollHistoryList.tsx | 42 +++- .../views/dialogs/polls/fetchPastPolls.ts | 65 +++--- .../views/dialogs/polls/usePollHistory.ts | 37 +++- src/i18n/strings/en_EN.json | 1 + .../dialogs/polls/PollHistoryDialog-test.tsx | 196 +++++++++++++++++- .../PollHistoryDialog-test.tsx.snap | 16 +- .../views/messages/MPollBody-test.tsx | 7 +- .../__snapshots__/MPollBody-test.tsx.snap | 2 - 10 files changed, 329 insertions(+), 60 deletions(-) diff --git a/res/css/views/dialogs/polls/_PollHistoryList.pcss b/res/css/views/dialogs/polls/_PollHistoryList.pcss index 6a0a003ce1e..0dc3419ea90 100644 --- a/res/css/views/dialogs/polls/_PollHistoryList.pcss +++ b/res/css/views/dialogs/polls/_PollHistoryList.pcss @@ -42,3 +42,14 @@ limitations under the License. justify-content: center; color: $secondary-content; } + +.mx_PollHistoryList_loading { + color: $secondary-content; + text-align: center; + + // center in all free space + // when there are no results + &.mx_PollHistoryList_noResultsYet { + margin: auto auto; + } +} diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index 432a852d608..1f10a24013d 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -23,7 +23,7 @@ import BaseDialog from "../BaseDialog"; import { IDialogProps } from "../IDialogProps"; import { PollHistoryList } from "./PollHistoryList"; import { PollHistoryFilter } from "./types"; -import { usePolls } from "./usePollHistory"; +import { usePollsWithRelations } from "./usePollHistory"; import { useFetchPastPolls } from "./fetchPastPolls"; type PollHistoryDialogProps = Pick & { @@ -44,17 +44,17 @@ const filterAndSortPolls = (polls: Map, filter: PollHistoryFilter) }; export const PollHistoryDialog: React.FC = ({ roomId, matrixClient, onFinished }) => { - const { polls } = usePolls(roomId, matrixClient); + const { polls } = usePollsWithRelations(roomId, matrixClient); const [filter, setFilter] = useState("ACTIVE"); const [pollStartEvents, setPollStartEvents] = useState(filterAndSortPolls(polls, filter)); + const room = matrixClient.getRoom(roomId)!; + const { isLoading } = useFetchPastPolls(room, matrixClient); useEffect(() => { + console.log("hhh", "new polls or filter", [...polls.values()]); setPollStartEvents(filterAndSortPolls(polls, filter)); }, [filter, polls]); - const room = matrixClient.getRoom(roomId)!; - const { isLoading } = useFetchPastPolls(room, matrixClient); - return (
@@ -63,7 +63,7 @@ export const PollHistoryDialog: React.FC = ({ roomId, ma isLoading={isLoading} filter={filter} onFilterChange={setFilter} - /> + />
); diff --git a/src/components/views/dialogs/polls/PollHistoryList.tsx b/src/components/views/dialogs/polls/PollHistoryList.tsx index 7c8714aeac9..a5261aec34c 100644 --- a/src/components/views/dialogs/polls/PollHistoryList.tsx +++ b/src/components/views/dialogs/polls/PollHistoryList.tsx @@ -16,18 +16,37 @@ limitations under the License. import React from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; +import classNames from "classnames"; -import PollListItem from "./PollListItem"; import { _t } from "../../../../languageHandler"; import { FilterTabGroup } from "../../elements/FilterTabGroup"; +import InlineSpinner from "../../elements/InlineSpinner"; import { PollHistoryFilter } from "./types"; +import PollListItem from "./PollListItem"; + +const LoadingPolls: React.FC<{ noResultsYet?: boolean }> = ({ noResultsYet }) => ( +
+ + {_t("Loading polls")} +
+); type PollHistoryListProps = { pollStartEvents: MatrixEvent[]; filter: PollHistoryFilter; onFilterChange: (filter: PollHistoryFilter) => void; + isLoading?: boolean; }; -export const PollHistoryList: React.FC = ({ pollStartEvents, filter, onFilterChange }) => { +export const PollHistoryList: React.FC = ({ + pollStartEvents, + filter, + isLoading, + onFilterChange, +}) => { return (
@@ -39,19 +58,24 @@ export const PollHistoryList: React.FC = ({ pollStartEvent { id: "ENDED", label: "Past polls" }, ]} /> - {!!pollStartEvents.length ? ( -
    - {pollStartEvents.map((pollStartEvent) => ( - - ))} -
- ) : ( + {!!pollStartEvents.length && ( + <> +
    + {pollStartEvents.map((pollStartEvent) => ( + + ))} + {isLoading && } +
+ + )} + {!pollStartEvents.length && !isLoading && ( {filter === "ACTIVE" ? _t("There are no active polls in this room") : _t("There are no past polls in this room")} )} + {!pollStartEvents.length && isLoading && }
); }; diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index ed2557d79e5..0b74dfa59fb 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -14,41 +14,47 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { useEffect, useState } from "react"; import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import { Direction, EventTimeline, EventTimelineSet, Room } from "matrix-js-sdk/src/matrix"; +import { EventTimeline, EventTimelineSet, Room } from "matrix-js-sdk/src/matrix"; import { Filter, IFilterDefinition } from "matrix-js-sdk/src/filter"; -import { useEffect, useRef, useState } from "react"; -const pagePolls = async (timelineSet: EventTimelineSet, matrixClient: MatrixClient, endOfHistoryPeriodTimestamp: number): Promise => { +const pagePolls = async ( + timelineSet: EventTimelineSet, + matrixClient: MatrixClient, + endOfHistoryPeriodTimestamp: number, +): Promise => { const liveTimeline = timelineSet.getLiveTimeline(); const events = liveTimeline.getEvents(); const oldestEventTimestamp = events[0]?.getTs() || Date.now(); const hasMorePages = !!liveTimeline.getPaginationToken(EventTimeline.BACKWARDS); - console.log('hhh token', liveTimeline.getPaginationToken(EventTimeline.BACKWARDS)); - console.log('hhhhh', { hasMorePages, oldestEventTimestamp, endOfHistoryPeriodTimestamp, oldestEventD: new Date(oldestEventTimestamp).toISOString() }) + if (!hasMorePages || oldestEventTimestamp <= endOfHistoryPeriodTimestamp) { return; } await matrixClient.paginateEventTimeline(liveTimeline, { - backwards: true + backwards: true, }); return pagePolls(timelineSet, matrixClient, endOfHistoryPeriodTimestamp); -} +}; const ONE_DAY_MS = 60000 * 60 * 24; -const useFilteredRoomHistory = (timelineSet: EventTimelineSet | null, matrixClient: MatrixClient, historyPeriodDays: number): { isLoading: boolean } => { - const [isLoading, setIsLoading] = useState(false); +const useFilteredRoomHistory = ( + timelineSet: EventTimelineSet | null, + matrixClient: MatrixClient, + historyPeriodDays: number, +): { isLoading: boolean } => { + const [isLoading, setIsLoading] = useState(true); useEffect(() => { - console.log('hhh useFilteredRoomHistory', {timelineSet}) if (!timelineSet) { return; } // @TODO(kerrya) maybe set this to start of current day - days - const endOfHistoryPeriodTimestamp = Date.now() - (ONE_DAY_MS * historyPeriodDays); + const endOfHistoryPeriodTimestamp = Date.now() - ONE_DAY_MS * historyPeriodDays; const doFetchHistory = async (): Promise => { setIsLoading(true); @@ -57,40 +63,41 @@ const useFilteredRoomHistory = (timelineSet: EventTimelineSet | null, matrixClie } finally { setIsLoading(false); } - } + }; doFetchHistory(); - }, [timelineSet]); + }, [timelineSet, historyPeriodDays, matrixClient]); return { isLoading }; -} - -export const useFetchPastPolls = (room: Room, matrixClient: MatrixClient, historyPeriodDays = 30): { isLoading: boolean; } => { - console.log('hhh', { room }); +}; - const filterDefinition: IFilterDefinition = { - room: { - timeline: { - types: [M_POLL_START.name, M_POLL_START.altName] - }, +const filterDefinition: IFilterDefinition = { + room: { + timeline: { + types: [M_POLL_START.name, M_POLL_START.altName, "m.room.encrypted"], }, - }; - + }, +}; +export const useFetchPastPolls = ( + room: Room, + matrixClient: MatrixClient, + historyPeriodDays = 30, +): { isLoading: boolean } => { const [timelineSet, setTimelineSet] = useState(null); useEffect(() => { const filter = new Filter(matrixClient.getSafeUserId()); filter.setDefinition(filterDefinition); - const getFilteredTimelineSet = async () => { + const getFilteredTimelineSet = async (): Promise => { const filterId = await matrixClient.getOrCreateFilter(`POLL_HISTORY_FILTER_${room.roomId}}`, filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); setTimelineSet(timelineSet); - } - + }; + getFilteredTimelineSet(); - }, [room]); + }, [room, matrixClient]); const { isLoading } = useFilteredRoomHistory(timelineSet, matrixClient, historyPeriodDays); return { isLoading }; -} \ No newline at end of file +}; diff --git a/src/components/views/dialogs/polls/usePollHistory.ts b/src/components/views/dialogs/polls/usePollHistory.ts index fa5bf220189..f3ed323a8ba 100644 --- a/src/components/views/dialogs/polls/usePollHistory.ts +++ b/src/components/views/dialogs/polls/usePollHistory.ts @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { useEffect, useState } from "react"; import { Poll, PollEvent } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { useEventEmitterState } from "../../../../hooks/useEventEmitter"; -import { useEffect } from "react"; /** * Get poll instances from a room @@ -41,7 +41,40 @@ export const usePolls = ( // copy room.polls map so changes can be detected const polls = useEventEmitterState(room, PollEvent.New, () => new Map(room.polls)); + return { polls }; +}; + +export const usePollsWithRelations = ( + roomId: string, + matrixClient: MatrixClient, +): { + polls: Map; +} => { + const { polls } = usePolls(roomId, matrixClient); + const [pollsWithRelations, setPollsWithRelations] = useState>(polls); + + useEffect(() => { + const onPollEnd = async (): Promise => { + // trigger rerender by creating a new poll map + setPollsWithRelations(new Map(polls)); + }; + if (polls) { + for (const poll of polls.values()) { + poll.on(PollEvent.End, onPollEnd); + poll.getResponses(); + } + setPollsWithRelations(polls); + } + () => { + if (polls) { + for (const poll of polls.values()) { + poll.off(PollEvent.End, onPollEnd); + } + } + }; + }, [polls, setPollsWithRelations]); + // @TODO(kerrya) watch polls for end events, trigger refiltering - return { polls }; + return { polls: pollsWithRelations }; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 46207ded9ef..7b7e94dceee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3144,6 +3144,7 @@ "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your Security Key.": "Access your secure message history and set up secure messaging by entering your Security Key.", "If you've forgotten your Security Key you can ": "If you've forgotten your Security Key you can ", + "Loading polls": "Loading polls", "There are no active polls in this room": "There are no active polls in this room", "There are no past polls in this room": "There are no past polls in this room", "Send custom account data event": "Send custom account data event", diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index b02bbb409b1..7f239e5018a 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -16,10 +16,13 @@ limitations under the License. import React from "react"; import { fireEvent, render } from "@testing-library/react"; -import { Room } from "matrix-js-sdk/src/matrix"; +import { Filter } from "matrix-js-sdk/src/filter"; +import { EventTimeline, Room } from "matrix-js-sdk/src/matrix"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/polls/PollHistoryDialog"; import { + flushPromises, getMockClientWithEventEmitter, makePollEndEvent, makePollStartEvent, @@ -30,6 +33,8 @@ import { } from "../../../../test-utils"; describe("", () => { + // 14.03.2022 16:15 + const now = 1647270879403; const userId = "@alice:domain.org"; const roomId = "!room:domain.org"; const mockClient = getMockClientWithEventEmitter({ @@ -37,8 +42,19 @@ describe("", () => { getRoom: jest.fn(), relations: jest.fn(), decryptEventIfNeeded: jest.fn(), + getOrCreateFilter: jest.fn(), + paginateEventTimeline: jest.fn(), + }); + let room = new Room(roomId, mockClient, userId); + + const expectedFilter = new Filter(userId); + expectedFilter.setDefinition({ + room: { + timeline: { + types: [M_POLL_START.name, M_POLL_START.altName, "m.room.encrypted"], + }, + }, }); - const room = new Room(roomId, mockClient, userId); const defaultProps = { roomId, @@ -52,10 +68,16 @@ describe("", () => { }); beforeEach(() => { + room = new Room(roomId, mockClient, userId); mockClient.getRoom.mockReturnValue(room); mockClient.relations.mockResolvedValue({ events: [] }); const timeline = room.getLiveTimeline(); jest.spyOn(timeline, "getEvents").mockReturnValue([]); + jest.spyOn(room, "getOrCreateFilteredTimelineSet"); + mockClient.getOrCreateFilter.mockResolvedValue(expectedFilter.filterId); + mockClient.paginateEventTimeline.mockReset().mockResolvedValue(false); + + jest.spyOn(Date, "now").mockReturnValue(now); }); afterAll(() => { @@ -68,21 +90,161 @@ describe("", () => { expect(() => getComponent()).toThrow("Cannot find room"); }); - it("renders a no polls message when there are no active polls in the timeline", () => { + it("renders a loading message while poll history is fetched", async () => { + const timelineSet = room.getOrCreateFilteredTimelineSet(expectedFilter); + const liveTimeline = timelineSet.getLiveTimeline(); + jest.spyOn(liveTimeline, "getPaginationToken").mockReturnValueOnce("test-pagination-token"); + + const { queryByText, getByText } = getComponent(); + + expect(mockClient.getOrCreateFilter).toHaveBeenCalledWith( + `POLL_HISTORY_FILTER_${room.roomId}}`, + expectedFilter, + ); + // no results not shown until loading finished + expect(queryByText("There are no active polls in this room")).not.toBeInTheDocument(); + expect(getByText("Loading polls")).toBeInTheDocument(); + + // flush filter creation request + await flushPromises(); + + expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS); + expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true }); + // only one page + expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1); + + // finished loading + expect(queryByText("Loading polls")).not.toBeInTheDocument(); + expect(getByText("There are no active polls in this room")).toBeInTheDocument(); + }); + + it("fetches poll history until end of timeline is reached while within time limit", async () => { + const timelineSet = room.getOrCreateFilteredTimelineSet(expectedFilter); + const liveTimeline = timelineSet.getLiveTimeline(); + + // mock three pages of timeline history + jest.spyOn(liveTimeline, "getPaginationToken") + .mockReturnValueOnce("test-pagination-token-1") + .mockReturnValueOnce("test-pagination-token-2") + .mockReturnValueOnce("test-pagination-token-3"); + + const { queryByText, getByText } = getComponent(); + + expect(mockClient.getOrCreateFilter).toHaveBeenCalledWith( + `POLL_HISTORY_FILTER_${room.roomId}}`, + expectedFilter, + ); + + // flush filter creation request + await flushPromises(); + // once per page + expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3); + + // finished loading + expect(queryByText("Loading polls")).not.toBeInTheDocument(); + expect(getByText("There are no active polls in this room")).toBeInTheDocument(); + }); + + it("fetches poll history until event older than history period is reached", async () => { + const timelineSet = room.getOrCreateFilteredTimelineSet(expectedFilter); + const liveTimeline = timelineSet.getLiveTimeline(); + const thirtyOneDaysAgoTs = now - 60000 * 60 * 24 * 31; + + jest.spyOn(liveTimeline, "getEvents") + .mockReturnValueOnce([]) + .mockReturnValueOnce([makePollStartEvent("Question?", userId, undefined, { ts: thirtyOneDaysAgoTs })]); + + // mock three pages of timeline history + jest.spyOn(liveTimeline, "getPaginationToken") + .mockReturnValueOnce("test-pagination-token-1") + .mockReturnValueOnce("test-pagination-token-2") + .mockReturnValueOnce("test-pagination-token-3"); + + getComponent(); + + // flush filter creation request + await flushPromises(); + // after first fetch the time limit is reached + // stop paging + expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1); + }); + + it("displays loader and list while paging timeline", async () => { + const timelineSet = room.getOrCreateFilteredTimelineSet(expectedFilter); + const liveTimeline = timelineSet.getLiveTimeline(); + const tenDaysAgoTs = now - 60000 * 60 * 24 * 10; + + jest.spyOn(liveTimeline, "getEvents").mockReset().mockReturnValue([]); + + // mock three pages of timeline history + jest.spyOn(liveTimeline, "getPaginationToken") + .mockReturnValueOnce("test-pagination-token-1") + .mockReturnValueOnce("test-pagination-token-2") + .mockReturnValueOnce("test-pagination-token-3"); + + // reference to pagination resolve, so we can assert between pages + let resolvePagination1: (value: boolean) => void | undefined; + let resolvePagination2: (value: boolean) => void | undefined; + mockClient.paginateEventTimeline + .mockImplementationOnce(async (_p) => { + const pollStart = makePollStartEvent("Question?", userId, undefined, { ts: now, id: "1" }); + jest.spyOn(liveTimeline, "getEvents").mockReturnValue([pollStart]); + room.processPollEvents([pollStart]); + return new Promise((resolve) => (resolvePagination1 = resolve)); + }) + .mockImplementationOnce(async (_p) => { + const pollStart = makePollStartEvent("Older question?", userId, undefined, { + ts: tenDaysAgoTs, + id: "2", + }); + jest.spyOn(liveTimeline, "getEvents").mockReturnValue([pollStart]); + room.processPollEvents([pollStart]); + return new Promise((resolve) => (resolvePagination2 = resolve)); + }); + + const { getByText, queryByText } = getComponent(); + + await flushPromises(); + + expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1); + + resolvePagination1!(true); + await flushPromises(); + + // first page has results, display immediately + expect(getByText("Question?")).toBeInTheDocument(); + // but we are still fetching history, diaply loader + expect(getByText("Loading polls")).toBeInTheDocument(); + + resolvePagination2(true); + await flushPromises(); + + // additional results addeds + expect(getByText("Older question?")).toBeInTheDocument(); + expect(getByText("Question?")).toBeInTheDocument(); + // finished paging + expect(queryByText("Loading polls")).not.toBeInTheDocument(); + + expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3); + }); + + it("renders a no polls message when there are no active polls in the room", async () => { const { getByText } = getComponent(); + await flushPromises(); expect(getByText("There are no active polls in this room")).toBeTruthy(); }); - it("renders a no past polls message when there are no past polls in the timeline", () => { + it("renders a no past polls message when there are no past polls in the room", async () => { const { getByText } = getComponent(); + await flushPromises(); fireEvent.click(getByText("Past polls")); expect(getByText("There are no past polls in this room")).toBeTruthy(); }); - it("renders a list of active polls when there are polls in the timeline", async () => { + it("renders a list of active polls when there are polls in the room", async () => { const timestamp = 1675300825090; const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: timestamp, id: "$1" }); const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: timestamp + 10000, id: "$2" }); @@ -99,6 +261,29 @@ describe("", () => { expect(queryByText("What?")).not.toBeInTheDocument(); }); + it("updates when new polls are added to the room", async () => { + const timestamp = 1675300825090; + const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: timestamp, id: "$1" }); + const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: timestamp + 10000, id: "$2" }); + // initially room has only one poll + await setupRoomWithPollEvents([pollStart1], [], [], mockClient, room); + + const { getByText } = getComponent(); + + expect(getByText("Question?")).toBeInTheDocument(); + + // add another poll + // paged history requests using cli.paginateEventTimeline + // call this with new events + await room.processPollEvents([pollStart2]); + // flush decryption promises + await flushPromises(); + + expect(getByText("Question?")).toBeInTheDocument(); + // list updated to include new poll + expect(getByText("Where?")).toBeInTheDocument(); + }); + it("filters ended polls", async () => { const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: 1675300825090, id: "$1" }); const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: 1675300725090, id: "$2" }); @@ -107,6 +292,7 @@ describe("", () => { await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); const { getByText, queryByText, getByTestId } = getComponent(); + await flushPromises(); expect(getByText("Question?")).toBeInTheDocument(); expect(getByText("Where?")).toBeInTheDocument(); diff --git a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap index 1672f66fd67..a6f508ca18c 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders a list of active polls when there are polls in the timeline 1`] = ` +exports[` renders a list of active polls when there are polls in the room 1`] = `
renders a list of active polls when there are pol Question? +
+
+
+
+ Loading polls +
diff --git a/test/components/views/messages/MPollBody-test.tsx b/test/components/views/messages/MPollBody-test.tsx index 7b43459ce3c..7c0dedc3172 100644 --- a/test/components/views/messages/MPollBody-test.tsx +++ b/test/components/views/messages/MPollBody-test.tsx @@ -75,13 +75,8 @@ describe("MPollBody", () => { // render without waiting for responses const renderResult = await newMPollBody(votes, [], undefined, undefined, false); - // votes still displayed - expect(votesCount(renderResult, "pizza")).toBe("2 votes"); - expect(votesCount(renderResult, "poutine")).toBe("1 vote"); - expect(votesCount(renderResult, "italian")).toBe("0 votes"); - expect(votesCount(renderResult, "wings")).toBe("1 vote"); // spinner rendered - expect(renderResult.getByTestId("totalVotes").innerHTML).toMatchSnapshot(); + expect(renderResult.getByTestId("spinner")).toBeTruthy(); }); it("renders no votes if none were made", async () => { diff --git a/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap index 3bce56a5403..3d6a99be8ae 100644 --- a/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap @@ -465,8 +465,6 @@ exports[`MPollBody renders a finished poll with no votes 1`] = `
`; -exports[`MPollBody renders a loader while responses are still loading 1`] = `"Based on 4 votes
"`; - exports[`MPollBody renders a poll that I have not voted in 1`] = `
Date: Wed, 15 Feb 2023 11:02:35 +1300 Subject: [PATCH 03/11] add comments, tidy --- .../views/dialogs/polls/PollHistoryDialog.tsx | 1 - .../views/dialogs/polls/usePollHistory.ts | 26 +++++++++++++++---- .../PollHistoryDialog-test.tsx.snap | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index 1f10a24013d..08725a3392d 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -51,7 +51,6 @@ export const PollHistoryDialog: React.FC = ({ roomId, ma const { isLoading } = useFetchPastPolls(room, matrixClient); useEffect(() => { - console.log("hhh", "new polls or filter", [...polls.values()]); setPollStartEvents(filterAndSortPolls(polls, filter)); }, [filter, polls]); diff --git a/src/components/views/dialogs/polls/usePollHistory.ts b/src/components/views/dialogs/polls/usePollHistory.ts index f3ed323a8ba..0ca1e62b202 100644 --- a/src/components/views/dialogs/polls/usePollHistory.ts +++ b/src/components/views/dialogs/polls/usePollHistory.ts @@ -22,6 +22,7 @@ import { useEventEmitterState } from "../../../../hooks/useEventEmitter"; /** * Get poll instances from a room + * Updates to include new polls * @param roomId - id of room to retrieve polls for * @param matrixClient - client * @returns {Map} - Map of Poll instances @@ -44,6 +45,17 @@ export const usePolls = ( return { polls }; }; +/** + * Get all poll instances from a room + * Fetch their responses (using cached poll responses) + * Updates on: + * - new polls added to room + * - new responses added to polls + * - changes to poll ended state + * @param roomId - id of room to retrieve polls for + * @param matrixClient - client + * @returns {Map} - Map of Poll instances + */ export const usePollsWithRelations = ( roomId: string, matrixClient: MatrixClient, @@ -54,27 +66,31 @@ export const usePollsWithRelations = ( const [pollsWithRelations, setPollsWithRelations] = useState>(polls); useEffect(() => { - const onPollEnd = async (): Promise => { + const onPollUpdate = async (): Promise => { // trigger rerender by creating a new poll map setPollsWithRelations(new Map(polls)); }; if (polls) { for (const poll of polls.values()) { - poll.on(PollEvent.End, onPollEnd); + // listen to changes in responses and end state + poll.on(PollEvent.End, onPollUpdate); + poll.on(PollEvent.Responses, onPollUpdate); + // trigger request to get all responses + // if they are not already in cache poll.getResponses(); } setPollsWithRelations(polls); } + // unsubscribe () => { if (polls) { for (const poll of polls.values()) { - poll.off(PollEvent.End, onPollEnd); + poll.off(PollEvent.End, onPollUpdate); + poll.off(PollEvent.Responses, onPollUpdate); } } }; }, [polls, setPollsWithRelations]); - // @TODO(kerrya) watch polls for end events, trigger refiltering - return { polls: pollsWithRelations }; }; diff --git a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap index a6f508ca18c..9b5b8e6a8dc 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap @@ -106,7 +106,7 @@ exports[` renders a list of active polls when there are pol class="mx_InlineSpinner" >
From cc0228ebe2edcf2a07042a04a1977397e5e1b1e2 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 12:15:19 +1300 Subject: [PATCH 04/11] more comments --- .../views/dialogs/polls/fetchPastPolls.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index 0b74dfa59fb..9bb985d0d34 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -20,6 +20,15 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { EventTimeline, EventTimelineSet, Room } from "matrix-js-sdk/src/matrix"; import { Filter, IFilterDefinition } from "matrix-js-sdk/src/filter"; +/** + * Page timeline backwards until either: + * - event older than endOfHistoryPeriodTimestamp is encountered + * - end of timeline is reached + * @param timelineSet - timelineset to page + * @param matrixClient - client + * @param endOfHistoryPeriodTimestamp - epoch timestamp to fetch until + * @returns void + */ const pagePolls = async ( timelineSet: EventTimelineSet, matrixClient: MatrixClient, @@ -42,6 +51,13 @@ const pagePolls = async ( }; const ONE_DAY_MS = 60000 * 60 * 24; +/** + * + * @param timelineSet - timelineset to page + * @param matrixClient - client + * @param historyPeriodDays - number of days of history to fetch, from current day + * @returns isLoading - true while fetching history + */ const useFilteredRoomHistory = ( timelineSet: EventTimelineSet | null, matrixClient: MatrixClient, @@ -53,7 +69,6 @@ const useFilteredRoomHistory = ( if (!timelineSet) { return; } - // @TODO(kerrya) maybe set this to start of current day - days const endOfHistoryPeriodTimestamp = Date.now() - ONE_DAY_MS * historyPeriodDays; const doFetchHistory = async (): Promise => { @@ -77,6 +92,14 @@ const filterDefinition: IFilterDefinition = { }, }, }; + +/** + * Fetch poll start events in the last N days of room history + * @param room - room to fetch history for + * @param matrixClient - client + * @param historyPeriodDays - number of days of history to fetch, from current day + * @returns isLoading - true while fetching history + */ export const useFetchPastPolls = ( room: Room, matrixClient: MatrixClient, From 771618754464934cff4f09e3e5b6f9a729fc2fd1 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 12:19:33 +1300 Subject: [PATCH 05/11] finish comment --- src/components/views/dialogs/polls/fetchPastPolls.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index 9bb985d0d34..d76919aecd4 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -52,13 +52,13 @@ const pagePolls = async ( const ONE_DAY_MS = 60000 * 60 * 24; /** - * + * Fetches timeline history for given number of days in past * @param timelineSet - timelineset to page * @param matrixClient - client * @param historyPeriodDays - number of days of history to fetch, from current day * @returns isLoading - true while fetching history */ -const useFilteredRoomHistory = ( +const useTimelineHistory = ( timelineSet: EventTimelineSet | null, matrixClient: MatrixClient, historyPeriodDays: number, @@ -120,7 +120,7 @@ export const useFetchPastPolls = ( getFilteredTimelineSet(); }, [room, matrixClient]); - const { isLoading } = useFilteredRoomHistory(timelineSet, matrixClient, historyPeriodDays); + const { isLoading } = useTimelineHistory(timelineSet, matrixClient, historyPeriodDays); return { isLoading }; }; From 58ee2d4caf9a428ad82c340d086c716ea5875aed Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 14:44:49 +1300 Subject: [PATCH 06/11] wait for responses to resolve before displaying in list --- .../views/dialogs/polls/PollHistoryDialog.tsx | 13 +++++++++---- .../views/dialogs/polls/fetchPastPolls.ts | 5 +++++ .../views/dialogs/polls/PollHistoryDialog-test.tsx | 10 ++++++++++ .../__snapshots__/PollHistoryDialog-test.tsx.snap | 14 -------------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index 08725a3392d..273dc6e25e7 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -35,7 +35,10 @@ const sortEventsByLatest = (left: MatrixEvent, right: MatrixEvent): number => ri const filterPolls = (filter: PollHistoryFilter) => (poll: Poll): boolean => - (filter === "ACTIVE") !== poll.isEnded; + // exclude polls while they are still loading + // to avoid jitter in list + !poll.isFetchingResponses && (filter === "ACTIVE") !== poll.isEnded; + const filterAndSortPolls = (polls: Map, filter: PollHistoryFilter): MatrixEvent[] => { return [...polls.values()] .filter(filterPolls(filter)) @@ -44,14 +47,16 @@ const filterAndSortPolls = (polls: Map, filter: PollHistoryFilter) }; export const PollHistoryDialog: React.FC = ({ roomId, matrixClient, onFinished }) => { + const room = matrixClient.getRoom(roomId)!; + const { isLoading } = useFetchPastPolls(room, matrixClient); const { polls } = usePollsWithRelations(roomId, matrixClient); const [filter, setFilter] = useState("ACTIVE"); const [pollStartEvents, setPollStartEvents] = useState(filterAndSortPolls(polls, filter)); - const room = matrixClient.getRoom(roomId)!; - const { isLoading } = useFetchPastPolls(room, matrixClient); + const [isLoadingPollResponses, setIsLoadingPollResponses] = useState(false); useEffect(() => { setPollStartEvents(filterAndSortPolls(polls, filter)); + setIsLoadingPollResponses([...polls.values()].some((poll) => poll.isFetchingResponses)); }, [filter, polls]); return ( @@ -59,7 +64,7 @@ export const PollHistoryDialog: React.FC = ({ roomId, ma
diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index d76919aecd4..8692465bb74 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -39,6 +39,11 @@ const pagePolls = async ( const oldestEventTimestamp = events[0]?.getTs() || Date.now(); const hasMorePages = !!liveTimeline.getPaginationToken(EventTimeline.BACKWARDS); + console.log("hhh", { + oldest: new Date(oldestEventTimestamp).toISOString(), + limit: new Date(endOfHistoryPeriodTimestamp).toISOString(), + }); + if (!hasMorePages || oldestEventTimestamp <= endOfHistoryPeriodTimestamp) { return; } diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index 7f239e5018a..fd2a72b5fb6 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -254,6 +254,9 @@ describe("", () => { const { container, queryByText, getByTestId } = getComponent(); + // flush relations calls for polls + await flushPromises(); + expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked(); expect(container).toMatchSnapshot(); @@ -270,6 +273,9 @@ describe("", () => { const { getByText } = getComponent(); + // wait for relations + await flushPromises(); + expect(getByText("Question?")).toBeInTheDocument(); // add another poll @@ -278,6 +284,10 @@ describe("", () => { await room.processPollEvents([pollStart2]); // flush decryption promises await flushPromises(); + // loading relations for new poll + expect(getByText("Loading polls")).toBeInTheDocument(); + // await relations for new poll + await flushPromises(); expect(getByText("Question?")).toBeInTheDocument(); // list updated to include new poll diff --git a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap index 9b5b8e6a8dc..d4641b5fe9c 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap @@ -99,20 +99,6 @@ exports[` renders a list of active polls when there are pol Question? -
-
-
-
- Loading polls -
From 4f1d063d272acdf0b80747794c4ab3312849c66a Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 15:20:09 +1300 Subject: [PATCH 07/11] dont use state for list --- .../views/dialogs/polls/PollHistoryDialog.tsx | 10 +++------- .../views/dialogs/polls/PollHistoryDialog-test.tsx | 4 ---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index 273dc6e25e7..8dee9131ed4 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixEvent, Poll } from "matrix-js-sdk/src/matrix"; @@ -51,13 +51,9 @@ export const PollHistoryDialog: React.FC = ({ roomId, ma const { isLoading } = useFetchPastPolls(room, matrixClient); const { polls } = usePollsWithRelations(roomId, matrixClient); const [filter, setFilter] = useState("ACTIVE"); - const [pollStartEvents, setPollStartEvents] = useState(filterAndSortPolls(polls, filter)); - const [isLoadingPollResponses, setIsLoadingPollResponses] = useState(false); - useEffect(() => { - setPollStartEvents(filterAndSortPolls(polls, filter)); - setIsLoadingPollResponses([...polls.values()].some((poll) => poll.isFetchingResponses)); - }, [filter, polls]); + const pollStartEvents = filterAndSortPolls(polls, filter); + const isLoadingPollResponses = [...polls.values()].some((poll) => poll.isFetchingResponses); return ( diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index fd2a72b5fb6..9a51e4e3c46 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -282,10 +282,6 @@ describe("", () => { // paged history requests using cli.paginateEventTimeline // call this with new events await room.processPollEvents([pollStart2]); - // flush decryption promises - await flushPromises(); - // loading relations for new poll - expect(getByText("Loading polls")).toBeInTheDocument(); // await relations for new poll await flushPromises(); From 7dd20749998bbc9d7098a61cd3f6023d8589cd33 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 15:47:19 +1300 Subject: [PATCH 08/11] return unsubscribe --- src/components/views/dialogs/polls/usePollHistory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/polls/usePollHistory.ts b/src/components/views/dialogs/polls/usePollHistory.ts index 0ca1e62b202..dafb241f198 100644 --- a/src/components/views/dialogs/polls/usePollHistory.ts +++ b/src/components/views/dialogs/polls/usePollHistory.ts @@ -82,7 +82,7 @@ export const usePollsWithRelations = ( setPollsWithRelations(polls); } // unsubscribe - () => { + return () => { if (polls) { for (const poll of polls.values()) { poll.off(PollEvent.End, onPollUpdate); From 09e7195da46870ad5b826d3e67736290b97c3ff8 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 15 Feb 2023 16:18:29 +1300 Subject: [PATCH 09/11] strict fixes --- src/components/views/dialogs/polls/fetchPastPolls.ts | 2 +- .../components/views/dialogs/polls/PollHistoryDialog-test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index 8692465bb74..d35f0ef2838 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -110,7 +110,7 @@ export const useFetchPastPolls = ( matrixClient: MatrixClient, historyPeriodDays = 30, ): { isLoading: boolean } => { - const [timelineSet, setTimelineSet] = useState(null); + const [timelineSet, setTimelineSet] = useState(null); useEffect(() => { const filter = new Filter(matrixClient.getSafeUserId()); diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index 9a51e4e3c46..b23c2498e1f 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -74,7 +74,7 @@ describe("", () => { const timeline = room.getLiveTimeline(); jest.spyOn(timeline, "getEvents").mockReturnValue([]); jest.spyOn(room, "getOrCreateFilteredTimelineSet"); - mockClient.getOrCreateFilter.mockResolvedValue(expectedFilter.filterId); + mockClient.getOrCreateFilter.mockResolvedValue(expectedFilter.filterId!); mockClient.paginateEventTimeline.mockReset().mockResolvedValue(false); jest.spyOn(Date, "now").mockReturnValue(now); @@ -216,7 +216,7 @@ describe("", () => { // but we are still fetching history, diaply loader expect(getByText("Loading polls")).toBeInTheDocument(); - resolvePagination2(true); + resolvePagination2!(true); await flushPromises(); // additional results addeds From 4753b4a748e873c586397c2d35bab816bab166ed Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 16 Feb 2023 10:37:42 +1300 Subject: [PATCH 10/11] unnecessary event type in filter --- src/components/views/dialogs/polls/fetchPastPolls.ts | 7 +------ .../views/dialogs/polls/PollHistoryDialog-test.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index d35f0ef2838..70554f25f65 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -39,11 +39,6 @@ const pagePolls = async ( const oldestEventTimestamp = events[0]?.getTs() || Date.now(); const hasMorePages = !!liveTimeline.getPaginationToken(EventTimeline.BACKWARDS); - console.log("hhh", { - oldest: new Date(oldestEventTimestamp).toISOString(), - limit: new Date(endOfHistoryPeriodTimestamp).toISOString(), - }); - if (!hasMorePages || oldestEventTimestamp <= endOfHistoryPeriodTimestamp) { return; } @@ -93,7 +88,7 @@ const useTimelineHistory = ( const filterDefinition: IFilterDefinition = { room: { timeline: { - types: [M_POLL_START.name, M_POLL_START.altName, "m.room.encrypted"], + types: [M_POLL_START.name, M_POLL_START.altName], }, }, }; diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index b23c2498e1f..062fe2d0d89 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -51,7 +51,7 @@ describe("", () => { expectedFilter.setDefinition({ room: { timeline: { - types: [M_POLL_START.name, M_POLL_START.altName, "m.room.encrypted"], + types: [M_POLL_START.name, M_POLL_START.altName], }, }, }); From bfc85a85eb000efb9761a16fa4cd92c967097002 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 17 Feb 2023 14:42:42 +1300 Subject: [PATCH 11/11] add catch --- src/components/views/dialogs/polls/fetchPastPolls.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/dialogs/polls/fetchPastPolls.ts b/src/components/views/dialogs/polls/fetchPastPolls.ts index 70554f25f65..1d045d3d079 100644 --- a/src/components/views/dialogs/polls/fetchPastPolls.ts +++ b/src/components/views/dialogs/polls/fetchPastPolls.ts @@ -19,6 +19,7 @@ import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { EventTimeline, EventTimelineSet, Room } from "matrix-js-sdk/src/matrix"; import { Filter, IFilterDefinition } from "matrix-js-sdk/src/filter"; +import { logger } from "matrix-js-sdk/src/logger"; /** * Page timeline backwards until either: @@ -75,6 +76,8 @@ const useTimelineHistory = ( setIsLoading(true); try { await pagePolls(timelineSet, matrixClient, endOfHistoryPeriodTimestamp); + } catch (error) { + logger.error("Failed to fetch room polls history", error); } finally { setIsLoading(false); }