[Release] Switch threads on for everyone (#9890)

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
This commit is contained in:
Germain 2023-01-12 12:34:40 +00:00 committed by GitHub
parent f38b5f62e5
commit f4ad9b29ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 38 additions and 38 deletions

View file

@ -77,7 +77,7 @@ describe("Polls", () => {
}; };
beforeEach(() => { beforeEach(() => {
cy.enableLabsFeature("feature_threadstable"); cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => { cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
}); });

View file

@ -29,7 +29,7 @@ describe("Threads", () => {
beforeEach(() => { beforeEach(() => {
// Default threads to ON for this spec // Default threads to ON for this spec
cy.enableLabsFeature("feature_threadstable"); cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => { cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
}); });

View file

@ -218,7 +218,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.pendingEventOrdering = PendingEventOrdering.Detached; opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true; opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadstable"); opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadenabled");
if (SettingsStore.getValue("feature_sliding_sync")) { if (SettingsStore.getValue("feature_sliding_sync")) {
const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url"); const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");

View file

@ -287,7 +287,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// and we check this in a hot code path. This is also cached in our // 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. // RoomContext, however we still need a fallback for roomless MessagePanels.
this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline"); this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
this.threadsEnabled = SettingsStore.getValue("feature_threadstable"); this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting( this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting(
"showTypingNotifications", "showTypingNotifications",

View file

@ -100,7 +100,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
return b.length - a.length; return b.length - a.length;
}); });
if (SettingsStore.getValue("feature_threadstable")) { if (SettingsStore.getValue("feature_threadenabled")) {
// Process all thread roots returned in this batch of search results // 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 // 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 result of results.results) {

View file

@ -1182,7 +1182,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
CHAT_EFFECTS.forEach((effect) => { CHAT_EFFECTS.forEach((effect) => {
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
// For initial threads launch, chat effects are disabled see #19731 // For initial threads launch, chat effects are disabled see #19731
if (!SettingsStore.getValue("feature_threadstable") || !ev.isRelation(THREAD_RELATION_TYPE.name)) { if (!SettingsStore.getValue("feature_threadenabled") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
} }

View file

@ -249,7 +249,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
const openFeedback = shouldShowFeedback() const openFeedback = shouldShowFeedback()
? () => { ? () => {
Modal.createDialog(BetaFeedbackDialog, { Modal.createDialog(BetaFeedbackDialog, {
featureId: "feature_threadstable", featureId: "feature_threadenabled",
}); });
} }
: null; : null;

View file

@ -1688,7 +1688,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
is very tied to the main room timeline, we are forcing the timeline to is very tied to the main room timeline, we are forcing the timeline to
send read receipts for threaded events */ send read receipts for threaded events */
const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread; const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread;
if (SettingsStore.getValue("feature_threadstable") && isThreadTimeline) { if (SettingsStore.getValue("feature_threadenabled") && isThreadTimeline) {
return 0; return 0;
} }
const index = this.state.events.findIndex((ev) => ev.getId() === evId); const index = this.state.events.findIndex((ev) => ev.getId() === evId);

View file

@ -71,7 +71,7 @@ const ReplyInThreadButton = ({ mxEvent, closeMenu }: IReplyInThreadButton) => {
if (Boolean(relationType) && relationType !== RelationType.Thread) return null; if (Boolean(relationType) && relationType !== RelationType.Thread) return null;
const onClick = (): void => { const onClick = (): void => {
if (!SettingsStore.getValue("feature_threadstable")) { if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({ dis.dispatch({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
initialTabId: UserTab.Labs, initialTabId: UserTab.Labs,
@ -640,7 +640,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
rightClick && rightClick &&
contentActionable && contentActionable &&
canSendMessages && canSendMessages &&
SettingsStore.getValue("feature_threadstable") && SettingsStore.getValue("feature_threadenabled") &&
Thread.hasServerSideSupport && Thread.hasServerSideSupport &&
timelineRenderingType !== TimelineRenderingType.Thread timelineRenderingType !== TimelineRenderingType.Thread
) { ) {

View file

@ -204,7 +204,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
const relationType = mxEvent?.getRelation()?.rel_type; const relationType = mxEvent?.getRelation()?.rel_type;
const hasARelation = !!relationType && relationType !== RelationType.Thread; const hasARelation = !!relationType && relationType !== RelationType.Thread;
const threadsEnabled = SettingsStore.getValue("feature_threadstable"); const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
if (!threadsEnabled && !Thread.hasServerSideSupport) { if (!threadsEnabled && !Thread.hasServerSideSupport) {
// hide the prompt if the user would only have degraded mode // hide the prompt if the user would only have degraded mode
@ -216,7 +216,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (!SettingsStore.getValue("feature_threadstable")) { if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({ dis.dispatch({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
initialTabId: UserTab.Labs, initialTabId: UserTab.Labs,
@ -252,7 +252,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
</div> </div>
{!hasARelation && ( {!hasARelation && (
<div className="mx_Tooltip_sub"> <div className="mx_Tooltip_sub">
{SettingsStore.getValue("feature_threadstable") {SettingsStore.getValue("feature_threadenabled")
? _t("Beta feature") ? _t("Beta feature")
: _t("Beta feature. Click to learn more.")} : _t("Beta feature. Click to learn more.")}
</div> </div>
@ -548,7 +548,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
); );
} }
} else if ( } else if (
SettingsStore.getValue("feature_threadstable") && SettingsStore.getValue("feature_threadenabled") &&
// Show thread icon even for deleted messages, but only within main timeline // Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === TimelineRenderingType.Room && this.context.timelineRenderingType === TimelineRenderingType.Room &&
this.props.mxEvent.getThread() this.props.mxEvent.getThread()

View file

@ -324,7 +324,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
); );
rightPanelPhaseButtons.set( rightPanelPhaseButtons.set(
RightPanelPhases.ThreadPanel, RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_threadstable") ? ( SettingsStore.getValue("feature_threadenabled") ? (
<HeaderButton <HeaderButton
key={RightPanelPhases.ThreadPanel} key={RightPanelPhases.ThreadPanel}
name="threadsButton" name="threadsButton"

View file

@ -387,7 +387,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
} }
} }
if (SettingsStore.getValue("feature_threadstable")) { if (SettingsStore.getValue("feature_threadenabled")) {
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread); this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
if (this.thread && !this.supportsThreadNotifications) { if (this.thread && !this.supportsThreadNotifications) {
@ -470,7 +470,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (this.props.showReactions) { if (this.props.showReactions) {
this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated); this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
} }
if (SettingsStore.getValue("feature_threadstable")) { if (SettingsStore.getValue("feature_threadenabled")) {
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread); this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
} }
this.threadState?.off(NotificationStateEvents.Update, this.onThreadStateUpdate); this.threadState?.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
@ -501,7 +501,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}; };
private get thread(): Thread | null { private get thread(): Thread | null {
if (!SettingsStore.getValue("feature_threadstable")) { if (!SettingsStore.getValue("feature_threadenabled")) {
return null; return null;
} }

View file

@ -68,7 +68,7 @@ export default class SearchResultTile extends React.Component<IProps> {
const layout = SettingsStore.getValue("layout"); const layout = SettingsStore.getValue("layout");
const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
const threadsEnabled = SettingsStore.getValue("feature_threadstable"); const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
for (let j = 0; j < timeline.length; j++) { for (let j = 0; j < timeline.length; j++) {
const mxEv = timeline[j]; const mxEv = timeline[j];

View file

@ -436,7 +436,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// For initial threads launch, chat effects are disabled // For initial threads launch, chat effects are disabled
// see #19731 // see #19731
const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name; const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) { if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
} }

View file

@ -112,7 +112,7 @@ export async function sendMessage(
// For initial threads launch, chat effects are disabled // For initial threads launch, chat effects are disabled
// see #19731 // see #19731
const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name; const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) { if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
} }

View file

@ -257,13 +257,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,
default: false, default: false,
}, },
"feature_threadstable": { "feature_threadenabled": {
isFeature: true, isFeature: true,
labsGroup: LabGroup.Messaging, labsGroup: LabGroup.Messaging,
controller: new ThreadBetaController(), controller: new ThreadBetaController(),
displayName: _td("Threaded messages"), displayName: _td("Threaded messages"),
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,
default: false, default: true,
betaInfo: { betaInfo: {
title: _td("Threaded messages"), title: _td("Threaded messages"),
caption: () => ( caption: () => (

View file

@ -65,7 +65,7 @@ export default class TypingStore {
if (SettingsStore.getValue("lowBandwidth")) return; if (SettingsStore.getValue("lowBandwidth")) return;
// Disable typing notification for threads for the initial launch // Disable typing notification for threads for the initial launch
// before we figure out a better user experience for them // before we figure out a better user experience for them
if (SettingsStore.getValue("feature_threadstable") && threadId) return; if (SettingsStore.getValue("feature_threadenabled") && threadId) return;
let currentTyping = this.typingStates[roomId]; let currentTyping = this.typingStates[roomId];
if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) { if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) {

View file

@ -278,10 +278,10 @@ export default class RightPanelStore extends ReadyWatchingStore {
// (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available) // (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available)
switch (card.phase) { switch (card.phase) {
case RightPanelPhases.ThreadPanel: case RightPanelPhases.ThreadPanel:
if (!SettingsStore.getValue("feature_threadstable")) return false; if (!SettingsStore.getValue("feature_threadenabled")) return false;
break; break;
case RightPanelPhases.ThreadView: case RightPanelPhases.ThreadView:
if (!SettingsStore.getValue("feature_threadstable")) return false; if (!SettingsStore.getValue("feature_threadenabled")) return false;
if (!card.state.threadHeadEvent) { if (!card.state.threadHeadEvent) {
logger.warn("removed card from right panel because of missing threadHeadEvent in card state"); logger.warn("removed card from right panel because of missing threadHeadEvent in card state");
} }

View file

@ -236,7 +236,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
// For initial threads launch, chat effects are disabled // For initial threads launch, chat effects are disabled
// see #19731 // see #19731
const isNotThread = content["m.relates_to"].rel_type !== THREAD_RELATION_TYPE.name; const isNotThread = content["m.relates_to"].rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) { if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
} }

View file

@ -176,7 +176,7 @@ export function makeReplyMixIn(ev?: MatrixEvent): IEventRelation {
}; };
if (ev.threadRootId) { if (ev.threadRootId) {
if (SettingsStore.getValue("feature_threadstable")) { if (SettingsStore.getValue("feature_threadenabled")) {
mixin.is_falling_back = false; mixin.is_falling_back = false;
} else { } else {
// Clients that do not offer a threading UI should behave as follows when replying, for best interaction // Clients that do not offer a threading UI should behave as follows when replying, for best interaction
@ -203,7 +203,7 @@ export function shouldDisplayReply(event: MatrixEvent): boolean {
const relation = event.getRelation(); const relation = event.getRelation();
if ( if (
SettingsStore.getValue("feature_threadstable") && SettingsStore.getValue("feature_threadenabled") &&
relation?.rel_type === THREAD_RELATION_TYPE.name && relation?.rel_type === THREAD_RELATION_TYPE.name &&
relation?.is_falling_back relation?.is_falling_back
) { ) {

View file

@ -62,7 +62,7 @@ export default class HTMLExporter extends Exporter {
this.mediaOmitText = !this.exportOptions.attachmentsIncluded this.mediaOmitText = !this.exportOptions.attachmentsIncluded
? _t("Media omitted") ? _t("Media omitted")
: _t("Media omitted - file size limit exceeded"); : _t("Media omitted - file size limit exceeded");
this.threadsEnabled = SettingsStore.getValue("feature_threadstable"); this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
} }
protected async getRoomAvatar() { protected async getRoomAvatar() {

View file

@ -174,7 +174,7 @@ describe("TimelinePanel", () => {
const getValueCopy = SettingsStore.getValue; const getValueCopy = SettingsStore.getValue;
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => { SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
if (name === "sendReadReceipts") return true; if (name === "sendReadReceipts") return true;
if (name === "feature_threadstable") return false; if (name === "feature_threadenabled") return false;
return getValueCopy(name); return getValueCopy(name);
}); });
@ -188,7 +188,7 @@ describe("TimelinePanel", () => {
const getValueCopy = SettingsStore.getValue; const getValueCopy = SettingsStore.getValue;
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => { SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
if (name === "sendReadReceipts") return false; if (name === "sendReadReceipts") return false;
if (name === "feature_threadstable") return false; if (name === "feature_threadenabled") return false;
return getValueCopy(name); return getValueCopy(name);
}); });
@ -365,7 +365,7 @@ describe("TimelinePanel", () => {
client.supportsExperimentalThreads = () => true; client.supportsExperimentalThreads = () => true;
const getValueCopy = SettingsStore.getValue; const getValueCopy = SettingsStore.getValue;
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => { SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
if (name === "feature_threadstable") return true; if (name === "feature_threadenabled") return true;
return getValueCopy(name); return getValueCopy(name);
}); });
@ -520,7 +520,7 @@ describe("TimelinePanel", () => {
}); });
it("renders when the last message is an undecryptable thread root", async () => { it("renders when the last message is an undecryptable thread root", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadstable"); jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadenabled");
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
client.isRoomEncrypted = () => true; client.isRoomEncrypted = () => true;

View file

@ -389,7 +389,7 @@ describe("<MessageActionBar />", () => {
describe("when threads feature is not enabled", () => { describe("when threads feature is not enabled", () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation( jest.spyOn(SettingsStore, "getValue").mockImplementation(
(setting) => setting !== "feature_threadstable", (setting) => setting !== "feature_threadenabled",
); );
}); });
@ -435,7 +435,7 @@ describe("<MessageActionBar />", () => {
describe("when threads feature is enabled", () => { describe("when threads feature is enabled", () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation( jest.spyOn(SettingsStore, "getValue").mockImplementation(
(setting) => setting === "feature_threadstable", (setting) => setting === "feature_threadenabled",
); );
}); });

View file

@ -44,7 +44,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
}); });
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => { jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
if (name === "feature_threadstable") return true; if (name === "feature_threadenabled") return true;
}); });
}); });

View file

@ -80,7 +80,7 @@ describe("EventTile", () => {
jest.spyOn(client, "getRoom").mockReturnValue(room); jest.spyOn(client, "getRoom").mockReturnValue(room);
jest.spyOn(client, "decryptEventIfNeeded").mockResolvedValue(); jest.spyOn(client, "decryptEventIfNeeded").mockResolvedValue();
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadstable"); jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadenabled");
mxEvent = mkMessage({ mxEvent = mkMessage({
room: room.roomId, room: room.roomId,