Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Render poll end events in timeline #10027

Merged
merged 31 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1fd5654
wip
Jan 9, 2023
2c01d2e
remove dupe
Jan 9, 2023
4ac6f5f
Merge branch 'develop' into kerry/poll-model
Jan 11, 2023
567e59e
Merge branch 'develop' into kerry/poll-model
Jan 16, 2023
6adc726
Merge branch 'develop' into kerry/poll-model
Jan 26, 2023
563bffb
use poll model relations in all cases
Jan 26, 2023
c87bf54
update mpollbody tests to use poll instance
Jan 26, 2023
4865dd5
update poll fetching login in pinned messages card
Jan 27, 2023
962eb5d
add pinned polls to room polls state
Jan 27, 2023
19de598
add spinner while relations are still loading
Jan 29, 2023
f97f70c
Merge branch 'develop' into kerry/poll-model
Jan 29, 2023
42a31ce
handle no poll in end poll dialog
Jan 29, 2023
903a2e0
Merge branch 'kerry/poll-model' of https://github.com/matrix-org/matr…
Jan 29, 2023
8512e51
strict errors
Jan 30, 2023
7ffd6e9
render a poll body that errors for poll end events
Jan 18, 2023
dca5835
add fetching logic to pollend tile
Jan 31, 2023
def87ec
extract poll testing utilities
Jan 31, 2023
6dbcd43
test mpollend
Jan 31, 2023
6d7cff6
Merge branch 'develop' into kerry/poll-model
Feb 1, 2023
08d2b3e
strict fix
Feb 1, 2023
a76d6a4
Merge branch 'develop' into kerry/poll-model
Feb 1, 2023
c345b7c
more strict fix
Feb 2, 2023
bc2f596
Merge branch 'kerry/poll-model' into psg-905/poll-end-in-timeline
Feb 2, 2023
7d818c1
strict fix for forwardref
Feb 2, 2023
282c0cb
Merge branch 'develop' into psg-905/poll-end-in-timeline
Feb 2, 2023
6aa4288
Merge branch 'develop' into psg-905/poll-end-in-timeline
Feb 3, 2023
a6a6128
update poll test utils
Feb 3, 2023
fe17871
Merge branch 'develop' into psg-905/poll-end-in-timeline
Feb 6, 2023
a0a0355
implicit anys
Feb 6, 2023
ba48cca
Merge branch 'develop' into psg-905/poll-end-in-timeline
Feb 7, 2023
e9bd892
tidy and add jsdoc
Feb 7, 2023
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
1 change: 1 addition & 0 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@
@import "./views/messages/_MLocationBody.pcss";
@import "./views/messages/_MNoticeBody.pcss";
@import "./views/messages/_MPollBody.pcss";
@import "./views/messages/_MPollEndBody.pcss";
@import "./views/messages/_MStickerBody.pcss";
@import "./views/messages/_MTextBody.pcss";
@import "./views/messages/_MVideoBody.pcss";
Expand Down
22 changes: 22 additions & 0 deletions res/css/views/messages/_MPollEndBody.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
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.
*/

.mx_MPollEndBody_icon {
height: 14px;
margin-right: $spacing-8;
vertical-align: middle;
color: $secondary-content;
}
6 changes: 3 additions & 3 deletions res/img/element-icons/room/composer/poll.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
98 changes: 98 additions & 0 deletions src/components/views/messages/MPollEndBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
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 React, { useEffect, useState, useContext } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
import { logger } from "matrix-js-sdk/src/logger";

import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { textForEvent } from "../../../TextForEvent";
import { IBodyProps } from "./IBodyProps";
import MPollBody from "./MPollBody";

const getRelatedPollStartEventId = (event: MatrixEvent): string | undefined => {
const relation = event.getRelation();
return relation?.event_id;
};

const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent; isLoadingPollStartEvent: boolean } => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a JSDoc block here explaining what this hook is doing?

const matrixClient = useContext(MatrixClientContext);
const [pollStartEvent, setPollStartEvent] = useState<MatrixEvent>();
const [isLoadingPollStartEvent, setIsLoadingPollStartEvent] = useState(false);

const pollStartEventId = getRelatedPollStartEventId(event);

useEffect(() => {
const room = matrixClient.getRoom(event.getRoomId());
const fetchPollStartEvent = async (roomId: string, pollStartEventId: string): Promise<void> => {
setIsLoadingPollStartEvent(true);
try {
const startEventJson = await matrixClient.fetchRoomEvent(roomId, pollStartEventId);
const startEvent = new MatrixEvent(startEventJson);
// add the poll to the room polls state
room?.processPollEvents([startEvent, event]);

if (startEvent.getSender() === event.getSender()) {
setPollStartEvent(startEvent);
}
} catch (error) {
logger.error("Failed to fetch related poll start event", error);
} finally {
setIsLoadingPollStartEvent(false);
}
};

if (pollStartEvent || !room || !pollStartEventId) {
return;
}

const timelineSet = room.getUnfilteredTimelineSet();
const localEvent = timelineSet
?.getTimelineForEvent(pollStartEventId)
?.getEvents()
.find((e) => e.getId() === pollStartEventId);

if (localEvent) {
if (localEvent.getSender() === event.getSender()) {
setPollStartEvent(localEvent);
}
} else {
// pollStartEvent is not in the current timeline,
// fetch it
fetchPollStartEvent(room.roomId, pollStartEventId);
}
}, [event, pollStartEventId, pollStartEvent, matrixClient]);

return { pollStartEvent, isLoadingPollStartEvent };
};

export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...props }, ref) => {
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);

if (!pollStartEvent) {
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent);
return (
<div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that actually required, or could we instead have a document fragment.
It does not look important and would keep the DOM structure leaner.

<PollIcon className="mx_MPollEndBody_icon" />
{!isLoadingPollStartEvent && pollEndFallbackMessage}
</div>
);
}

return <MPollBody mxEvent={pollStartEvent} {...props} />;
});
5 changes: 4 additions & 1 deletion src/components/views/messages/MessageEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React, { createRef } from "react";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { MatrixEventEvent } from "matrix-js-sdk/src/models/event";

import SettingsStore from "../../../settings/SettingsStore";
Expand All @@ -37,6 +37,7 @@ import MVoiceOrAudioBody from "./MVoiceOrAudioBody";
import MVideoBody from "./MVideoBody";
import MStickerBody from "./MStickerBody";
import MPollBody from "./MPollBody";
import { MPollEndBody } from "./MPollEndBody";
import MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody";
Expand Down Expand Up @@ -73,6 +74,8 @@ const baseEvTypes = new Map<string, React.ComponentType<Partial<IBodyProps>>>([
[EventType.Sticker, MStickerBody],
[M_POLL_START.name, MPollBody],
[M_POLL_START.altName, MPollBody],
[M_POLL_END.name, MPollEndBody],
[M_POLL_END.altName, MPollEndBody],
[M_BEACON_INFO.name, MBeaconBody],
[M_BEACON_INFO.altName, MBeaconBody],
]);
Expand Down
10 changes: 8 additions & 2 deletions src/events/EventTileFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { Optional } from "matrix-events-sdk";
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall";

Expand Down Expand Up @@ -99,6 +99,8 @@ const EVENT_TILE_TYPES = new Map<string, Factory>([
[EventType.Sticker, MessageEventFactory],
[M_POLL_START.name, MessageEventFactory],
[M_POLL_START.altName, MessageEventFactory],
[M_POLL_END.name, MessageEventFactory],
[M_POLL_END.altName, MessageEventFactory],
[EventType.KeyVerificationCancel, KeyVerificationConclFactory],
[EventType.KeyVerificationDone, KeyVerificationConclFactory],
[EventType.CallInvite, LegacyCallEventFactory], // note that this requires a special factory type
Expand Down Expand Up @@ -412,7 +414,11 @@ export function renderReplyTile(
// XXX: this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
export function isMessageEvent(ev: MatrixEvent): boolean {
return messageTypes.includes(ev.getType() as EventType) || M_POLL_START.matches(ev.getType());
return (
messageTypes.includes(ev.getType() as EventType) ||
M_POLL_START.matches(ev.getType()) ||
M_POLL_END.matches(ev.getType())
);
}

export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boolean): boolean {
Expand Down
4 changes: 2 additions & 2 deletions src/events/forward/getForwardableEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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 { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";

Expand All @@ -26,7 +26,7 @@ import { VoiceBroadcastInfoEventType } from "../../voice-broadcast/types";
* If an event is not forwardable return null
*/
export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => {
if (M_POLL_START.matches(event.getType())) {
if (M_POLL_START.matches(event.getType()) || M_POLL_END.matches(event.getType())) {
return null;
}

Expand Down
3 changes: 2 additions & 1 deletion src/utils/EventRenderingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { IContent } from "matrix-js-sdk/src/matrix";

Expand All @@ -41,6 +41,7 @@ const calcIsInfoMessage = (
eventType !== EventType.Sticker &&
eventType !== EventType.RoomCreate &&
!M_POLL_START.matches(eventType) &&
!M_POLL_END.matches(eventType) &&
!M_BEACON_INFO.matches(eventType) &&
!(eventType === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started)
);
Expand Down
3 changes: 2 additions & 1 deletion src/utils/EventUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
Expand Down Expand Up @@ -57,6 +57,7 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean {
} else if (
mxEvent.getType() === "m.sticker" ||
M_POLL_START.matches(mxEvent.getType()) ||
M_POLL_END.matches(mxEvent.getType()) ||
M_BEACON_INFO.matches(mxEvent.getType()) ||
(mxEvent.getType() === VoiceBroadcastInfoEventType &&
mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ describe("<PollHistoryDialog />", () => {
expect(getByText("There are no polls in this room")).toBeTruthy();
});

it("renders a list of polls when there are polls in the timeline", () => {
const pollStart1 = makePollStartEvent("Question?", userId, undefined, 1675300825090, "$1");
const pollStart2 = makePollStartEvent("Where?", userId, undefined, 1675300725090, "$2");
const pollStart3 = makePollStartEvent("What?", userId, undefined, 1675200725090, "$3");
it("renders a list of polls when there are polls in the timeline", async () => {
const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: 1675300825090, id: "$1" });
const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: 1675300725090, id: "$2" });
const pollStart3 = makePollStartEvent("What?", userId, undefined, { ts: 1675200725090, id: "$3" });
const message = new MatrixEvent({
type: "m.room.message",
content: {},
Expand Down
Loading