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

Remove threads labs flag and the ability to disable threads #9878

Merged
merged 12 commits into from
Feb 20, 2023
3 changes: 1 addition & 2 deletions cypress/e2e/polls/polls.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Copyright 2022 - 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.
Expand Down Expand Up @@ -83,7 +83,6 @@ describe("Polls", () => {
};

beforeEach(() => {
cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
Expand Down
38 changes: 1 addition & 37 deletions cypress/e2e/threads/threads.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Copyright 2022 - 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.
Expand All @@ -19,17 +19,10 @@ limitations under the License.
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";

function markWindowBeforeReload(): void {
// mark our window object to "know" when it gets reloaded
cy.window().then((w) => (w.beforeReload = true));
}

describe("Threads", () => {
let homeserver: HomeserverInstance;

beforeEach(() => {
// Default threads to ON for this spec
cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
Expand All @@ -44,35 +37,6 @@ describe("Threads", () => {
cy.stopHomeserver(homeserver);
});

it("should reload when enabling threads beta", () => {
markWindowBeforeReload();

// Turn off
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);

cy.leaveBeta("Threaded messages");
cy.wait(1000);
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});

cy.get(".mx_MatrixChat", { timeout: 15000 }); // wait for the app
markWindowBeforeReload();

// Turn on
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);

cy.joinBeta("Threaded messages");
cy.wait(1000);
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});
});

it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(homeserver, {
Expand Down
5 changes: 4 additions & 1 deletion src/DateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
return prevDate.getFullYear() === nextDate.getFullYear();
}

export function wantsDateSeparator(prevEventDate: Date | undefined, nextEventDate: Date | undefined): boolean {
export function wantsDateSeparator(
prevEventDate: Date | null | undefined,
nextEventDate: Date | null | undefined,
): boolean {
if (!nextEventDate || !prevEventDate) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd.
Copyright 2017, 2018, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 - 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.
Expand Down Expand Up @@ -218,7 +218,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.threadSupport = SettingsStore.getValue("feature_threadenabled");
opts.threadSupport = true;

if (SettingsStore.getValue("feature_sliding_sync")) {
const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");
Expand Down
2 changes: 1 addition & 1 deletion src/Unread.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2015 - 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.
Expand Down
24 changes: 4 additions & 20 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2016 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2016 - 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.
Expand Down Expand Up @@ -75,7 +75,6 @@ export function shouldFormContinuation(
prevEvent: MatrixEvent | null,
mxEvent: MatrixEvent,
showHiddenEvents: boolean,
threadsEnabled: boolean,
timelineRenderingType?: TimelineRenderingType,
): boolean {
if (timelineRenderingType === TimelineRenderingType.ThreadsList) return false;
Expand Down Expand Up @@ -105,7 +104,6 @@ export function shouldFormContinuation(

// Thread summaries in the main timeline should break up a continuation on both sides
if (
threadsEnabled &&
(hasThreadSummary(mxEvent) || hasThreadSummary(prevEvent)) &&
timelineRenderingType !== TimelineRenderingType.Thread
) {
Expand Down Expand Up @@ -259,7 +257,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
private readReceiptsByUserId: Record<string, IReadReceiptForUser> = {};

private readonly _showHiddenEvents: boolean;
private readonly threadsEnabled: boolean;
private isMounted = false;

private readMarkerNode = createRef<HTMLLIElement>();
Expand Down Expand Up @@ -287,7 +284,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// and we check this in a hot code path. This is also cached in our
// RoomContext, however we still need a fallback for roomless MessagePanels.
this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");

this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting(
"showTypingNotifications",
Expand Down Expand Up @@ -464,7 +460,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {

// TODO: Implement granular (per-room) hide options
public shouldShowEvent(mxEv: MatrixEvent, forceHideEvents = false): boolean {
if (this.props.hideThreadedMessages && this.threadsEnabled && this.props.room) {
if (this.props.hideThreadedMessages && this.props.room) {
const { shouldLiveInRoom } = this.props.room.eventShouldLiveIn(mxEv, this.props.events);
if (!shouldLiveInRoom) {
return false;
Expand Down Expand Up @@ -720,25 +716,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
willWantDateSeparator ||
mxEv.getSender() !== nextEv.getSender() ||
getEventDisplayInfo(nextEv, this.showHiddenEvents).isInfoMessage ||
!shouldFormContinuation(
mxEv,
nextEv,
this.showHiddenEvents,
this.threadsEnabled,
this.context.timelineRenderingType,
);
!shouldFormContinuation(mxEv, nextEv, this.showHiddenEvents, this.context.timelineRenderingType);
}

// is this a continuation of the previous message?
const continuation =
!wantsDateSeparator &&
shouldFormContinuation(
prevEvent,
mxEv,
this.showHiddenEvents,
this.threadsEnabled,
this.context.timelineRenderingType,
);
shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents, this.context.timelineRenderingType);

const eventId = mxEv.getId();
const highlight = eventId === this.props.highlightedEventId;
Expand Down
33 changes: 14 additions & 19 deletions src/components/structures/RoomSearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2015 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2015 - 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.
Expand Down Expand Up @@ -34,7 +34,6 @@ import ResizeNotifier from "../../utils/ResizeNotifier";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import RoomContext from "../../contexts/RoomContext";
import SettingsStore from "../../settings/SettingsStore";

const DEBUG = false;
let debuglog = function (msg: string): void {};
Expand Down Expand Up @@ -100,23 +99,19 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
return b.length - a.length;
});

if (SettingsStore.getValue("feature_threadenabled")) {
// Process all thread roots returned in this batch of search results
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship
for (const result of results.results) {
for (const event of result.context.getTimeline()) {
const bundledRelationship =
event.getServerAggregatedRelation<IThreadBundledRelationship>(
THREAD_RELATION_TYPE.name,
);
if (!bundledRelationship || event.getThread()) continue;
const room = client.getRoom(event.getRoomId());
const thread = room?.findThreadForEvent(event);
if (thread) {
event.setThread(thread);
} else {
room?.createThread(event.getId()!, event, [], true);
}
for (const result of results.results) {
for (const event of result.context.getTimeline()) {
const bundledRelationship =
event.getServerAggregatedRelation<IThreadBundledRelationship>(
THREAD_RELATION_TYPE.name,
);
if (!bundledRelationship || event.getThread()) continue;
const room = client.getRoom(event.getRoomId());
const thread = room?.findThreadForEvent(event);
if (thread) {
event.setThread(thread);
} else {
room?.createThread(event.getId()!, event, [], true);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2019 - 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.
Expand Down Expand Up @@ -1194,7 +1194,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
CHAT_EFFECTS.forEach((effect) => {
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
// For initial threads launch, chat effects are disabled see #19731
if (!SettingsStore.getValue("feature_threadenabled") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
if (!ev.isRelation(THREAD_RELATION_TYPE.name)) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2016 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2016 - 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.
Expand Down Expand Up @@ -1689,8 +1689,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
/* Threads do not have server side support for read receipts and the concept
is very tied to the main room timeline, we are forcing the timeline to
send read receipts for threaded events */
const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread;
if (SettingsStore.getValue("feature_threadenabled") && isThreadTimeline) {
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
return 0;
}
const index = this.state.events.findIndex((ev) => ev.getId() === evId);
Expand Down
11 changes: 2 additions & 9 deletions src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Copyright 2019 Michael Telatynski <[email protected]>
Copyright 2015 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2015 - 2023 The Matrix.org Foundation C.I.C.
Copyright 2021 - 2022 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -56,7 +56,6 @@ import { getForwardableEvent } from "../../../events/forward/getForwardableEvent
import { getShareableLocationEvent } from "../../../events/location/getShareableLocationEvent";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import { CardContext } from "../right_panel/context";
import { UserTab } from "../dialogs/UserTab";

interface IReplyInThreadButton {
mxEvent: MatrixEvent;
Expand All @@ -71,12 +70,7 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent, closeMen
if (Boolean(relationType) && relationType !== RelationType.Thread) return null;

const onClick = (): void => {
if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
});
} else if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
dis.dispatch<ShowThreadPayload>({
action: Action.ShowThread,
rootEvent: mxEvent.getThread().rootEvent,
Expand Down Expand Up @@ -639,7 +633,6 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
rightClick &&
contentActionable &&
canSendMessages &&
SettingsStore.getValue("feature_threadenabled") &&
Thread.hasServerSideSupport &&
timelineRenderingType !== TimelineRenderingType.Thread
) {
Expand Down
25 changes: 2 additions & 23 deletions src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <[email protected]>
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 - 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.
Expand All @@ -20,7 +20,6 @@ import React, { ReactElement, useCallback, useContext, useEffect } from "react";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
import classNames from "classnames";
import { MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { Thread } from "matrix-js-sdk/src/models/thread";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";

import { Icon as ContextMenuIcon } from "../../../../res/img/element-icons/context-menu.svg";
Expand Down Expand Up @@ -54,7 +53,6 @@ import { CardContext } from "../right_panel/context";
import { shouldDisplayReply } from "../../../utils/Reply";
import { Key } from "../../../Keyboard";
import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
import { UserTab } from "../dialogs/UserTab";
import { Action } from "../../../dispatcher/actions";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import useFavouriteMessages from "../../../hooks/useFavouriteMessages";
Expand Down Expand Up @@ -204,24 +202,13 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent }) => {

const relationType = mxEvent?.getRelation()?.rel_type;
const hasARelation = !!relationType && relationType !== RelationType.Thread;
const threadsEnabled = SettingsStore.getValue("feature_threadenabled");

if (!threadsEnabled && !Thread.hasServerSideSupport) {
// hide the prompt if the user would only have degraded mode
return null;
}

const onClick = (e: React.MouseEvent): void => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();

if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
});
} else if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
defaultDispatcher.dispatch<ShowThreadPayload>({
action: Action.ShowThread,
rootEvent: mxEvent.getThread().rootEvent,
Expand Down Expand Up @@ -250,13 +237,6 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent }) => {
? _t("Reply in thread")
: _t("Can't create a thread from an event with an existing relation")}
</div>
{!hasARelation && (
<div className="mx_Tooltip_sub">
{SettingsStore.getValue("feature_threadenabled")
? _t("Beta feature")
: _t("Beta feature. Click to learn more.")}
</div>
)}
</>
}
title={
Expand Down Expand Up @@ -548,7 +528,6 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
);
}
} else if (
SettingsStore.getValue("feature_threadenabled") &&
// Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === TimelineRenderingType.Room &&
this.props.mxEvent.getThread()
Expand Down
Loading