From 2e1f80592f75fbfd77a451e3df7b9f55ce43f481 Mon Sep 17 00:00:00 2001 From: Caleb Roseland Date: Mon, 6 Jul 2026 11:49:44 -0500 Subject: [PATCH 1/2] fix(channel-bookmarks): replace feature flag check with min server version ChannelBookmarks FF was removed from the server (GA in v10.1). Replace the observeConfigBooleanValue('FeatureFlagChannelBookmarks') check with an isMinimumServerVersion(v, 10, 1, 0) check so bookmarks work on v10.1+ servers regardless of the FF field being present. MM-61199 --- app/actions/remote/channel_bookmark.test.ts | 11 ++++------- app/actions/remote/channel_bookmark.ts | 6 ++++-- app/constants/versions.ts | 1 + app/queries/servers/features.ts | 8 +++++++- app/screens/channel/header/index.ts | 3 ++- app/screens/channel/index.tsx | 5 ++--- app/screens/channel_info/index.ts | 4 ++-- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/actions/remote/channel_bookmark.test.ts b/app/actions/remote/channel_bookmark.test.ts index af3d87a66d..b9248f04a9 100644 --- a/app/actions/remote/channel_bookmark.test.ts +++ b/app/actions/remote/channel_bookmark.test.ts @@ -49,9 +49,7 @@ describe('channel bookmarks', () => { await DatabaseManager.init([serverUrl]); operator = DatabaseManager.serverDatabases[serverUrl]!.operator; await operator.handleConfigs({ - configs: [ - {id: 'FeatureFlagChannelBookmarks', value: 'true'}, - ], + configs: [{id: 'Version', value: '10.1.0'}], configsToDelete: [], prepareRecordsOnly: false, }); @@ -87,11 +85,9 @@ describe('channel bookmarks', () => { expect(bookmarkSpy).not.toHaveBeenCalled(); }); - it('fetchChannelBookmarks - feature flag disabled', async () => { + it('fetchChannelBookmarks - old server version', async () => { await operator.handleConfigs({ - configs: [ - {id: 'FeatureFlagChannelBookmarks', value: 'false'}, - ], + configs: [{id: 'Version', value: '10.0.0'}], configsToDelete: [], prepareRecordsOnly: false, }); @@ -99,6 +95,7 @@ describe('channel bookmarks', () => { expect(result).toBeDefined(); expect(result.bookmarks).toBeDefined(); expect(result.bookmarks?.length).toBe(0); + expect(mockClient.getChannelBookmarksForChannel).not.toHaveBeenCalled(); }); it('createChannelBookmark - handle not found database', async () => { diff --git a/app/actions/remote/channel_bookmark.ts b/app/actions/remote/channel_bookmark.ts index 41601ca109..3f3d6c2d84 100644 --- a/app/actions/remote/channel_bookmark.ts +++ b/app/actions/remote/channel_bookmark.ts @@ -1,12 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {CHANNEL_BOOKMARKS_VERSION} from '@constants/versions'; import DatabaseManager from '@database/manager'; import NetworkManager from '@managers/network_manager'; import websocketManager from '@managers/websocket_manager'; import {getBookmarksSince, getChannelBookmarkById} from '@queries/servers/channel_bookmark'; import {getConfigValue, getLicense} from '@queries/servers/system'; import {getFullErrorMessage} from '@utils/errors'; +import {isMinimumServerVersion} from '@utils/helpers'; import {logError} from '@utils/log'; import {forceLogoutIfNecessary} from './session'; @@ -16,10 +18,10 @@ export async function fetchChannelBookmarks(serverUrl: string, channelId: string const client = NetworkManager.getClient(serverUrl); const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); - const bookmarksEnabled = (await getConfigValue(database, 'FeatureFlagChannelBookmarks')) === 'true'; + const serverVersion = await getConfigValue(database, 'Version'); const isLicensed = (await getLicense(database))?.IsLicensed === 'true'; - if (!bookmarksEnabled || !isLicensed) { + if (!isMinimumServerVersion(serverVersion, ...CHANNEL_BOOKMARKS_VERSION) || !isLicensed) { return {bookmarks: []}; } diff --git a/app/constants/versions.ts b/app/constants/versions.ts index 05a70da3ed..c88db0199d 100644 --- a/app/constants/versions.ts +++ b/app/constants/versions.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. export const GM_AS_DM_VERSION = [9, 1, 0]; +export const CHANNEL_BOOKMARKS_VERSION = [10, 1, 0]; export const OS_VERSION = { ANDROID: 'android', diff --git a/app/queries/servers/features.ts b/app/queries/servers/features.ts index b1359a0dbc..7dcd470e83 100644 --- a/app/queries/servers/features.ts +++ b/app/queries/servers/features.ts @@ -4,7 +4,7 @@ import {of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import {GM_AS_DM_VERSION} from '@constants/versions'; +import {CHANNEL_BOOKMARKS_VERSION, GM_AS_DM_VERSION} from '@constants/versions'; import {isMinimumServerVersion} from '@utils/helpers'; import {observeConfigValue} from './system'; @@ -16,3 +16,9 @@ export const observeHasGMasDMFeature = (database: Database) => { switchMap((v) => of$(isMinimumServerVersion(v, ...GM_AS_DM_VERSION))), ); }; + +export const observeIsChannelBookmarksEnabled = (database: Database) => { + return observeConfigValue(database, 'Version').pipe( + switchMap((v) => of$(isMinimumServerVersion(v, ...CHANNEL_BOOKMARKS_VERSION))), + ); +}; diff --git a/app/screens/channel/header/index.ts b/app/screens/channel/header/index.ts index f4cdf487a8..6886f7b3b1 100644 --- a/app/screens/channel/header/index.ts +++ b/app/screens/channel/header/index.ts @@ -11,6 +11,7 @@ import {queryPlaybookRunsPerChannel} from '@playbooks/database/queries/run'; import {observeIsPlaybooksEnabled} from '@playbooks/database/queries/version'; import {observeChannel, observeChannelInfo, observeIsChannelAutotranslated} from '@queries/servers/channel'; import {observeCanAddBookmarks, queryBookmarks} from '@queries/servers/channel_bookmark'; +import {observeIsChannelBookmarksEnabled} from '@queries/servers/features'; import {observeConfigBooleanValue, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; import {observeIsUserLanguageSupportedByAutotranslation, observeUser} from '@queries/servers/user'; import { @@ -94,7 +95,7 @@ const enhanced = withObservables(['channelId'], ({channelId, database}: OwnProps distinctUntilChanged(), ); - const isBookmarksEnabled = observeConfigBooleanValue(database, 'FeatureFlagChannelBookmarks'); + const isBookmarksEnabled = observeIsChannelBookmarksEnabled(database); const canAddBookmarks = observeCanAddBookmarks(database, channelId); const activeRuns = isPlaybooksEnabled.pipe( diff --git a/app/screens/channel/index.tsx b/app/screens/channel/index.tsx index e485487391..cd04007f93 100644 --- a/app/screens/channel/index.tsx +++ b/app/screens/channel/index.tsx @@ -10,11 +10,10 @@ import {Preferences} from '@constants'; import {withServerUrl} from '@context/server'; import {observeCurrentChannel} from '@queries/servers/channel'; import {queryBookmarks} from '@queries/servers/channel_bookmark'; -import {observeHasGMasDMFeature} from '@queries/servers/features'; +import {observeHasGMasDMFeature, observeIsChannelBookmarksEnabled} from '@queries/servers/features'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeScheduledPostCountForChannel} from '@queries/servers/scheduled_post'; import { - observeConfigBooleanValue, observeCurrentChannelId, observeCurrentUserId, } from '@queries/servers/system'; @@ -35,7 +34,7 @@ const enhanced = withObservables([], ({database, serverUrl}: EnhanceProps) => { const channelType = observeCurrentChannel(database).pipe(switchMap((c) => of$(c?.type))); const currentUserId = observeCurrentUserId(database); const hasGMasDMFeature = observeHasGMasDMFeature(database); - const isBookmarksEnabled = observeConfigBooleanValue(database, 'FeatureFlagChannelBookmarks'); + const isBookmarksEnabled = observeIsChannelBookmarksEnabled(database); const hasBookmarks = (count: number) => of$(count > 0); const includeBookmarkBar = channelId.pipe( combineLatestWith(isBookmarksEnabled), diff --git a/app/screens/channel_info/index.ts b/app/screens/channel_info/index.ts index 0dae5da331..142e944814 100644 --- a/app/screens/channel_info/index.ts +++ b/app/screens/channel_info/index.ts @@ -12,9 +12,9 @@ import {withServerUrl} from '@context/server'; import {observeIsPlaybooksEnabled} from '@playbooks/database/queries/version'; import {observeChannelAutotranslation, observeCurrentChannel} from '@queries/servers/channel'; import {observeCanAddBookmarks} from '@queries/servers/channel_bookmark'; +import {observeIsChannelBookmarksEnabled} from '@queries/servers/features'; import {observeCanManageChannelAutotranslations, observeCanManageChannelMembers, observeCanManageChannelSettings, observePermissionForChannel, observePermissionForTeam} from '@queries/servers/role'; import { - observeConfigBooleanValue, observeConfigValue, observeCurrentChannelId, observeCurrentTeamId, @@ -241,7 +241,7 @@ const enhanced = withObservables([], ({serverUrl, database}: Props) => { distinctUntilChanged(), ); - const isBookmarksEnabled = observeConfigBooleanValue(database, 'FeatureFlagChannelBookmarks'); + const isBookmarksEnabled = observeIsChannelBookmarksEnabled(database); const canAddBookmarks = channelId.pipe( switchMap((cId) => { From 65c536c3a7d5712d1e9adbb5c6aaeb175c14f004 Mon Sep 17 00:00:00 2001 From: Caleb Roseland Date: Mon, 6 Jul 2026 14:02:45 -0500 Subject: [PATCH 2/2] fix(channel-bookmarks): fold license check into observeIsChannelBookmarksEnabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensures the observable is consistent with the fetchChannelBookmarks guard — unlicensed servers correctly suppress bookmark UI, not just the network call. MM-69705 --- app/queries/servers/features.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/queries/servers/features.ts b/app/queries/servers/features.ts index 7dcd470e83..f1b91ce264 100644 --- a/app/queries/servers/features.ts +++ b/app/queries/servers/features.ts @@ -1,13 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {of as of$} from 'rxjs'; +import {combineLatest, of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {CHANNEL_BOOKMARKS_VERSION, GM_AS_DM_VERSION} from '@constants/versions'; import {isMinimumServerVersion} from '@utils/helpers'; -import {observeConfigValue} from './system'; +import {observeConfigValue, observeLicense} from './system'; import type {Database} from '@nozbe/watermelondb'; @@ -18,7 +18,13 @@ export const observeHasGMasDMFeature = (database: Database) => { }; export const observeIsChannelBookmarksEnabled = (database: Database) => { - return observeConfigValue(database, 'Version').pipe( - switchMap((v) => of$(isMinimumServerVersion(v, ...CHANNEL_BOOKMARKS_VERSION))), + return combineLatest([ + observeConfigValue(database, 'Version'), + observeLicense(database), + ]).pipe( + switchMap(([v, license]) => of$( + isMinimumServerVersion(v, ...CHANNEL_BOOKMARKS_VERSION) && + license?.IsLicensed === 'true', + )), ); };