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

Allow pinning polls #7922

Merged
merged 4 commits into from
Mar 4, 2022
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
50 changes: 50 additions & 0 deletions src/components/views/rooms/PinnedEventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ limitations under the License.

import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations";
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { M_POLL_START, M_POLL_RESPONSE, M_POLL_END } from "matrix-events-sdk";

import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
Expand Down Expand Up @@ -52,6 +56,51 @@ export default class PinnedEventTile extends React.Component<IProps> {
});
};

// For event types like polls that use relations, we fetch those manually on
// mount and store them here, exposing them through getRelationsForEvent
private relations = new Map<string, Map<string, Relations>>();
private getRelationsForEvent = (
eventId: string,
relationType: RelationType | string,
eventType: EventType | string,
): Relations => {
if (eventId === this.props.event.getId()) {
return this.relations.get(relationType)?.get(eventType);
}
};

async componentDidMount() {
// Fetch poll responses
if (M_POLL_START.matches(this.props.event.getType())) {
const eventId = this.props.event.getId();
const roomId = this.props.event.getRoomId();
const room = this.context.getRoom(roomId);

try {
await Promise.all(
[M_POLL_RESPONSE.name, M_POLL_RESPONSE.altName, M_POLL_END.name, M_POLL_END.altName]
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than having to enumerate all the possible event types that can reference a poll start you can use the bundled relationship.

Looking at a poll event source, you'll see. Fetching all the chunks listed here should give you a comprehensive view of a poll and its responses

{
  "type": "org.matrix.msc3381.poll.start",
  // ...
  "content": {
    // ...
  },
  "origin_server_ts": 1,
  "unsigned": {
    "age": 1,
    "m.relations": {
      "m.reference": {
        "chunk": [
          {
            "event_id": "$hJMr8vQFUVOrvnvHOv6X1hMh0vQE9jBroewXkQTHTAo"
          }
          // ...
        ],
        "next_batch": "t38-2062_0_0_0_0_0_0_0_0"
      }
    }
  },
  "event_id": "$event123",
  "user_id": "@alice:matrix.org",
  "age": 2324572194
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Also wondering whether this should live on the JS sdk level, as there are many event types that could face the same issue

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that approach would probably perform worse, though, since it'd involve a separate request for every individual vote rather than the fixed number of requests that this currently uses. Unless there's a way to batch multiple /event calls together?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair enough. I first wrote a comment suggesting you use the new /relations endpoint only to realise this is exactly what you're doing.
I'm not entirely sold on listing the event types as you're doing it above, but can't think of a better way to do that with what's in the spec at the moment

.map(async eventType => {
const { events } = await this.context.relations(
roomId, eventId, RelationType.Reference, eventType,
);

const relations = new Relations(RelationType.Reference, eventType, room);
if (!this.relations.has(RelationType.Reference)) {
this.relations.set(RelationType.Reference, new Map<string, Relations>());
}
this.relations.get(RelationType.Reference).set(eventType, relations);

relations.setTargetEvent(this.props.event);
events.forEach(event => relations.addEvent(event));
}),
);
} catch (err) {
logger.error(`Error fetching responses to pinned poll ${eventId} in room ${roomId}`);
logger.error(err);
}
}
}

render() {
const sender = this.props.event.getSender();

Expand Down Expand Up @@ -84,6 +133,7 @@ export default class PinnedEventTile extends React.Component<IProps> {
<div className="mx_PinnedEventTile_message">
<MessageEvent
mxEvent={this.props.event}
getRelationsForEvent={this.getRelationsForEvent}
// @ts-ignore - complaining that className is invalid when it's not
className="mx_PinnedEventTile_body"
maxImageHeight={150}
Expand Down
13 changes: 12 additions & 1 deletion src/utils/PinningUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@ limitations under the License.
*/

import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { M_POLL_START } from "matrix-events-sdk";

export default class PinningUtils {
/**
* Event types that may be pinned.
*/
static pinnableEventTypes: (EventType | string)[] = [
EventType.RoomMessage,
M_POLL_START.name,
M_POLL_START.altName,
];

/**
* Determines if the given event may be pinned.
* @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise.
*/
static isPinnable(event: MatrixEvent): boolean {
if (!event) return false;
if (event.getType() !== "m.room.message") return false;
if (!this.pinnableEventTypes.includes(event.getType())) return false;
if (event.isRedacted()) return false;

return true;
Expand Down