[MM-69591] Remove WebSocketEventScope feature flag (#37384)

* Remove WebSocketEventScope feature flag gating on server

Leave the typing/reaction WebSocket event scoping permanently enabled.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove WebSocketEventScope feature flag usage in webapp

Always update the active channel/team/thread over the WebSocket so the
event scoping stays permanently enabled.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove WebSocketEventScope feature flag from e2e default config

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
This commit is contained in:
cursor[bot]
2026-07-08 21:18:18 +00:00
committed by GitHub
co-authored by mattermost-code Cursor Agent Jesse Hallam
parent af43bfdbb0
commit f0abe8a596
12 changed files with 12 additions and 28 deletions
@@ -800,7 +800,7 @@ const defaultServerConfig: AdminConfig = {
EnableExportDirectDownload: false,
MoveThreadsEnabled: false,
CloudDedicatedExportUI: false,
WebSocketEventScope: true,
NotificationMonitoring: true,
ExperimentalAuditSettingsSystemConsoleUI: true,
CustomProfileAttributes: true,
AttributeBasedAccessControl: true,
+5 -6
View File
@@ -963,12 +963,11 @@ func (wc *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
if chID := msg.GetBroadcast().ChannelId; chID != "" {
// For typing/reaction_added/reaction_removed events, we don't send them to users
// who don't have that channel or thread opened.
if wc.Platform.Config().FeatureFlags.WebSocketEventScope &&
slices.Contains([]model.WebsocketEventType{
model.WebsocketEventTyping,
model.WebsocketEventReactionAdded,
model.WebsocketEventReactionRemoved,
}, msg.EventType()) && wc.notInChannel(chID) && wc.notInThread(chID) {
if slices.Contains([]model.WebsocketEventType{
model.WebsocketEventTyping,
model.WebsocketEventReactionAdded,
model.WebsocketEventReactionRemoved,
}, msg.EventType()) && wc.notInChannel(chID) && wc.notInThread(chID) {
return false
}
+2 -2
View File
@@ -38,7 +38,7 @@ type FeatureFlags struct {
CloudDedicatedExportUI bool
WebSocketEventScope bool
NotificationMonitoring bool
ExperimentalAuditSettingsSystemConsoleUI bool
@@ -155,7 +155,7 @@ func (f *FeatureFlags) SetDefaults() {
f.EnableExportDirectDownload = false
f.MoveThreadsEnabled = false
f.CloudDedicatedExportUI = false
f.WebSocketEventScope = true
f.NotificationMonitoring = true
f.ExperimentalAuditSettingsSystemConsoleUI = true
f.CustomProfileAttributes = true
f.AttributeBasedAccessControl = true
@@ -55,7 +55,6 @@ describe('components/channel_view', () => {
isCloud: false,
goToLastViewedChannel: jest.fn(),
isFirstAdmin: false,
enableWebSocketEventScope: false,
isChannelBookmarksEnabled: false,
missingChannelRole: false,
fetchIsRestrictedDM: jest.fn(),
@@ -100,7 +100,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
// TODO: debounce
if (prevProps.channelId !== this.props.channelId && this.props.enableWebSocketEventScope) {
if (prevProps.channelId !== this.props.channelId) {
WebSocketClient.updateActiveChannel(this.props.channelId);
}
@@ -37,7 +37,6 @@ function mapStateToProps(state: GlobalState) {
const config = getConfig(state);
const enableOnboardingFlow = config.EnableOnboardingFlow === 'true';
const enableWebSocketEventScope = config.FeatureFlagWebSocketEventScope === 'true';
const missingChannelRole = isMissingChannelRoles(state, channel);
@@ -49,7 +48,6 @@ function mapStateToProps(state: GlobalState) {
isCloud: getLicense(state).Cloud === 'true',
teamUrl: getCurrentRelativeTeamUrl(state),
isFirstAdmin: isFirstAdmin(state),
enableWebSocketEventScope,
canRestrictDirectMessage: config.RestrictDirectMessage === 'team' && (channel?.type === 'D' || channel?.type === 'G'),
restrictDirectMessage: channel ? state.entities.channels.restrictedDMs[channel.id] : false,
isChannelBookmarksEnabled: getIsChannelBookmarksEnabled(state),
@@ -38,7 +38,6 @@ function mapStateToProps(state: GlobalState) {
const products = state.plugins.components.Product || [];
const [unreadTeamsSet, mentionsInTeamMap, teamHasUrgentMap] = getTeamsUnreadStatuses(state);
const enableWebSocketEventScope = config.FeatureFlagWebSocketEventScope === 'true';
return {
currentTeamId: getCurrentTeamId(state),
@@ -52,7 +51,6 @@ function mapStateToProps(state: GlobalState) {
unreadTeamsSet,
mentionsInTeamMap,
teamHasUrgentMap,
enableWebSocketEventScope,
};
}
@@ -123,7 +123,7 @@ export class TeamSidebar extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
// TODO: debounce
if (prevProps.currentTeamId !== this.props.currentTeamId && this.props.enableWebSocketEventScope) {
if (prevProps.currentTeamId !== this.props.currentTeamId) {
WebSocketClient.updateActiveTeam(this.props.currentTeamId);
}
}
@@ -6,7 +6,6 @@ import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import type {Channel} from '@mattermost/types/channels';
import type {ClientConfig} from '@mattermost/types/config';
import type {UserThread} from '@mattermost/types/threads';
import {fetchRHSAppsBindings} from 'mattermost-redux/actions/apps';
@@ -14,7 +13,6 @@ import {getNewestPostThread, getPostThread} from 'mattermost-redux/actions/posts
import {getThread as fetchThread, updateThreadRead} from 'mattermost-redux/actions/threads';
import {appsEnabled} from 'mattermost-redux/selectors/entities/apps';
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPost, makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts';
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
@@ -46,8 +44,6 @@ function makeMapStateToProps() {
const socketStatus = getSocketStatus(state);
const highlightedPostId = getHighlightedPostId(state);
const selectedPostFocusedAt = getSelectedPostFocussedAt(state);
const config: Partial<ClientConfig> = getConfig(state);
const enableWebSocketEventScope = config.FeatureFlagWebSocketEventScope === 'true';
let postIds: string[] = [];
let userThread: UserThread | null = null;
@@ -73,7 +69,6 @@ function makeMapStateToProps() {
channel,
highlightedPostId,
selectedPostFocusedAt,
enableWebSocketEventScope,
lastUpdateAt,
};
};
@@ -87,7 +87,6 @@ describe('components/threading/ThreadViewer', () => {
appsEnabled: true,
rootPostId: post.id,
isThreadView: true,
enableWebSocketEventScope: false,
lastUpdateAt: 1234,
};
@@ -55,7 +55,6 @@ export type Props = Attrs & {
isThreadView: boolean;
inputPlaceholder?: string;
rootPostId: string;
enableWebSocketEventScope: boolean;
lastUpdateAt: number;
};
@@ -85,9 +84,7 @@ export default class ThreadViewer extends React.PureComponent<Props, State> {
}
public componentWillUnmount() {
if (this.props.enableWebSocketEventScope) {
WebSocketClient.updateActiveThread(this.props.isThreadView, '');
}
WebSocketClient.updateActiveThread(this.props.isThreadView, '');
}
public componentDidUpdate(prevProps: Props) {
@@ -203,7 +200,7 @@ export default class ThreadViewer extends React.PureComponent<Props, State> {
await this.fetchThread();
}
if (this.props.channel && this.props.enableWebSocketEventScope) {
if (this.props.channel) {
WebSocketClient.updateActiveThread(this.props.isThreadView, this.props.channel?.id);
}
this.setState({isLoading: false});
-1
View File
@@ -133,7 +133,6 @@ export type ClientConfig = {
FeatureFlagPermissionPolicies: string;
FeatureFlagChannelPermissionPolicies: string;
FeatureFlagPolicySimulation: string;
FeatureFlagWebSocketEventScope: string;
FeatureFlagContentFlagging: string;
FeatureFlagClassificationMarkings: string;
FeatureFlagPropertyFieldRank: string;