diff --git a/e2e-tests/cypress/tests/support/api/preference.d.ts b/e2e-tests/cypress/tests/support/api/preference.d.ts index f53fb974b1..d26c5fe447 100644 --- a/e2e-tests/cypress/tests/support/api/preference.d.ts +++ b/e2e-tests/cypress/tests/support/api/preference.d.ts @@ -126,18 +126,6 @@ declare namespace Cypress { */ apiSaveCloudTrialBannerPreference(userId: string, name: string, value: string): Chainable; - /** - * Save actions menu preference. - * See https://api.mattermost.com/#tag/preferences/paths/~1users~1{user_id}~1preferences/put - * @param {string} userId - User ID - * @param {string} value - true (default) or false - * @returns {Response} response: Cypress-chainable response which should have successful HTTP status of 200 OK to continue or pass. - * - * @example - * cy.apiSaveActionsMenuPreference('user-id', true); - */ - apiSaveActionsMenuPreference(userId: string, value: boolean): Chainable; - /** * Save show trial modal. * See https://api.mattermost.com/#tag/preferences/paths/~1users~1{user_id}~1preferences/put diff --git a/e2e-tests/cypress/tests/support/api/preference.js b/e2e-tests/cypress/tests/support/api/preference.js index f3f4ae0fca..ba517bea35 100644 --- a/e2e-tests/cypress/tests/support/api/preference.js +++ b/e2e-tests/cypress/tests/support/api/preference.js @@ -256,17 +256,6 @@ Cypress.Commands.add('apiSaveCloudTrialBannerPreference', (userId, name, value) return cy.apiSaveUserPreference([preference], userId); }); -Cypress.Commands.add('apiSaveActionsMenuPreference', (userId, value = true) => { - const preference = { - user_id: userId, - category: 'actions_menu', - name: 'actions_menu_tutorial_state', - value: JSON.stringify({actions_menu_modal_viewed: value}), - }; - - return cy.apiSaveUserPreference([preference], userId); -}); - Cypress.Commands.add('apiSaveStartTrialModal', (userId, value = 'true') => { const preference = { user_id: userId, @@ -402,12 +391,6 @@ Cypress.Commands.add('apiDisableTutorials', (userId) => { name: userId, value: '999', }, - { - user_id: userId, - category: 'actions_menu', - name: 'actions_menu_tutorial_state', - value: '{"actions_menu_modal_viewed":true}', - }, { user_id: userId, category: 'drafts', diff --git a/e2e-tests/cypress/tests/support/api/user.js b/e2e-tests/cypress/tests/support/api/user.js index 325658dc21..155339e58f 100644 --- a/e2e-tests/cypress/tests/support/api/user.js +++ b/e2e-tests/cypress/tests/support/api/user.js @@ -234,7 +234,6 @@ Cypress.Commands.add('apiCreateUser', ({ prefix = 'user', createAt = 0, bypassTutorial = true, - hideActionsMenu = true, hideOnboarding = true, bypassWhatsNewModal = true, user = null, @@ -265,10 +264,6 @@ Cypress.Commands.add('apiCreateUser', ({ cy.apiDisableTutorials(createdUser.id); } - if (hideActionsMenu) { - cy.apiSaveActionsMenuPreference(createdUser.id, true); - } - if (hideOnboarding) { cy.apiSaveOnboardingPreference(createdUser.id, 'hide', 'true'); cy.apiSaveOnboardingPreference(createdUser.id, 'skip', 'true'); diff --git a/e2e-tests/cypress/tests/support/index.js b/e2e-tests/cypress/tests/support/index.js index 4784ec0cce..4c52ecbde7 100644 --- a/e2e-tests/cypress/tests/support/index.js +++ b/e2e-tests/cypress/tests/support/index.js @@ -261,7 +261,6 @@ function resetUserPreference(userId) { cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_open', 'false'); cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_show', 'false'); cy.apiSaveCloudTrialBannerPreference(userId, 'trial', 'max_days_banner'); - cy.apiSaveActionsMenuPreference(userId); cy.apiSaveSkipStepsPreference(userId, 'true'); cy.apiSaveStartTrialModal(userId, 'true'); cy.apiSaveUnreadScrollPositionPreference(userId, 'start_from_left_off'); diff --git a/webapp/channels/src/actions/views/channel.test.js b/webapp/channels/src/actions/views/channel.test.js index ab17d8444b..cde0d101e4 100644 --- a/webapp/channels/src/actions/views/channel.test.js +++ b/webapp/channels/src/actions/views/channel.test.js @@ -81,7 +81,9 @@ describe('channel view actions', () => { }, }, general: { - config: {}, + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, serverVersion: '5.12.0', }, roles: { @@ -589,7 +591,7 @@ describe('channel view actions', () => { describe('markChannelAsReadOnFocus', () => { test('should mark channel as read when channel is not manually unread', async () => { - test = mockStore(initialState); + store = mockStore(initialState); await store.dispatch(Actions.markChannelAsReadOnFocus(channel1.id)); diff --git a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/cloud_trial_banner.tsx b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/cloud_trial_banner.tsx index 9ca6f70782..e3bbbcabca 100644 --- a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/cloud_trial_banner.tsx +++ b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/cloud_trial_banner.tsx @@ -43,7 +43,7 @@ const CloudTrialBanner = ({trialEndDate}: Props): JSX.Element | null => { // the banner when dismissed, will be dismissed for 10 days const bannerIsStillDismissed = diffDays < 0; - shouldShowBanner = storedDismissedEndDate && bannerIsStillDismissed; + shouldShowBanner = Boolean(storedDismissedEndDate) && bannerIsStillDismissed; } const [showBanner, setShowBanner] = useState(shouldShowBanner); diff --git a/webapp/channels/src/components/announcement_bar/cloud_trial_announcement_bar/index.ts b/webapp/channels/src/components/announcement_bar/cloud_trial_announcement_bar/index.ts index 68e796bfa8..48743de4a3 100644 --- a/webapp/channels/src/components/announcement_bar/cloud_trial_announcement_bar/index.ts +++ b/webapp/channels/src/components/announcement_bar/cloud_trial_announcement_bar/index.ts @@ -20,9 +20,9 @@ import type {GlobalState} from 'types/store'; import CloudTrialAnnouncementBar from './cloud_trial_announcement_bar'; -function mapStateToProps(state: GlobalState) { - const getCategory = makeGetCategory(); +const getCloudTrialBannerPreferences = makeGetCategory('getCloudTrialBannerPreferences', Preferences.CLOUD_TRIAL_BANNER); +function mapStateToProps(state: GlobalState) { const subscription = state.entities.cloud.subscription; const isCloud = getLicense(state).Cloud === 'true'; let isFreeTrial = false; @@ -43,7 +43,7 @@ function mapStateToProps(state: GlobalState) { currentUser: getCurrentUser(state), isCloud, subscription, - preferences: getCategory(state, Preferences.CLOUD_TRIAL_BANNER), + preferences: getCloudTrialBannerPreferences(state), }; } diff --git a/webapp/channels/src/components/announcement_bar/cloud_trial_ended_announcement_bar/index.tsx b/webapp/channels/src/components/announcement_bar/cloud_trial_ended_announcement_bar/index.tsx index 74be3cd8ae..3fdb0cf7b5 100644 --- a/webapp/channels/src/components/announcement_bar/cloud_trial_ended_announcement_bar/index.tsx +++ b/webapp/channels/src/components/announcement_bar/cloud_trial_ended_announcement_bar/index.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React from 'react'; import {FormattedMessage, defineMessages} from 'react-intl'; import {useSelector, useDispatch} from 'react-redux'; @@ -30,14 +30,13 @@ import type {GlobalState} from 'types/store'; import AnnouncementBar from '../default_announcement_bar'; +const getCloudTrialEndBannerPreferences = makeGetCategory('getCloudTrialEndBannerPreferences', Preferences.CLOUD_TRIAL_END_BANNER); + const CloudTrialEndAnnouncementBar: React.FC = () => { const limits = useGetLimits(); const subscription = useGetSubscription(); - const getCategory = useMemo(makeGetCategory, []); const dispatch = useDispatch(); - const preferences = useSelector((state: GlobalState) => - getCategory(state, Preferences.CLOUD_TRIAL_END_BANNER), - ); + const preferences = useSelector(getCloudTrialEndBannerPreferences); const currentUser = useSelector((state: GlobalState) => getCurrentUser(state), ); diff --git a/webapp/channels/src/components/announcement_bar/overage_users_banner/index.tsx b/webapp/channels/src/components/announcement_bar/overage_users_banner/index.tsx index 92355ce9c9..735eca64b7 100644 --- a/webapp/channels/src/components/announcement_bar/overage_users_banner/index.tsx +++ b/webapp/channels/src/components/announcement_bar/overage_users_banner/index.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React from 'react'; import {FormattedMessage} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; @@ -10,7 +10,7 @@ import type {PreferenceType} from '@mattermost/types/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; import {getLicense} from 'mattermost-redux/selectors/entities/general'; -import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; +import {getOverageBannerPreferences} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users'; import AnnouncementBar from 'components/announcement_bar/default_announcement_bar'; @@ -46,9 +46,8 @@ const OverageUsersBanner = () => { const license = useSelector(getLicense); const seatsPurchased = parseInt(license.Users, 10); const isCloud = useSelector(isCurrentLicenseCloud); - const getPreferencesCategory = useMemo(makeGetCategory, []); const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); - const overagePreferences = useSelector((state: GlobalState) => getPreferencesCategory(state, Preferences.OVERAGE_USERS_BANNER)); + const overagePreferences = useSelector(getOverageBannerPreferences); const activeUsers = ((stats || {})[StatTypes.TOTAL_USERS]) as number || 0; const { isBetween5PercerntAnd10PercentPurchasedSeats, diff --git a/webapp/channels/src/components/announcement_bar/show_start_trial_modal/show_start_trial_modal.tsx b/webapp/channels/src/components/announcement_bar/show_start_trial_modal/show_start_trial_modal.tsx index 3ccad07cbc..a865628739 100644 --- a/webapp/channels/src/components/announcement_bar/show_start_trial_modal/show_start_trial_modal.tsx +++ b/webapp/channels/src/components/announcement_bar/show_start_trial_modal/show_start_trial_modal.tsx @@ -1,14 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {useEffect, useMemo} from 'react'; +import {useEffect} from 'react'; import {useSelector, useDispatch} from 'react-redux'; -import type {PreferenceType} from '@mattermost/types/preferences'; - import {savePreferences} from 'mattermost-redux/actions/preferences'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; +import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users'; import {trackEvent} from 'actions/telemetry_actions'; @@ -29,7 +27,6 @@ import type {GlobalState} from 'types/store'; const ShowStartTrialModal = () => { const isUserAdmin = useSelector((state: GlobalState) => isCurrentUserSystemAdmin(state)); const openStartTrialFormModal = useOpenStartTrialFormModal(); - const getCategory = useMemo(makeGetCategory, []); const dispatch = useDispatch(); @@ -40,7 +37,7 @@ const ShowStartTrialModal = () => { const installationDate = useSelector((state: GlobalState) => getConfig(state).InstallationDate); const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); - const preferences = useSelector((state) => getCategory(state, Preferences.START_TRIAL_MODAL)); + const hadAdminDismissedModal = useSelector((state: GlobalState) => getBool(state, Preferences.START_TRIAL_MODAL, Constants.TRIAL_MODAL_AUTO_SHOWN)); const prevTrialLicense = useSelector((state: GlobalState) => state.entities.admin.prevTrialLicense); const currentLicense = useSelector(getLicense); @@ -77,7 +74,6 @@ const ShowStartTrialModal = () => { const now = new Date().getTime(); const hasEnvMoreThan6Hours = now > installationDatePlus6Hours; const hasEnvMoreThan10Users = Number(totalUsers) > userThreshold; - const hadAdminDismissedModal = preferences.some((pref: PreferenceType) => pref.name === Constants.TRIAL_MODAL_AUTO_SHOWN && pref.value === TRUE); if (isUserAdmin && !isBenefitsModalOpened && hasEnvMoreThan10Users && hasEnvMoreThan6Hours && !hadAdminDismissedModal && !isLicensedOrPreviousLicensed) { openStartTrialFormModal({trackingLocation: 'show_start_trial_modal'}, handleOnClose); trackEvent( diff --git a/webapp/channels/src/components/common/hooks/usePreference.ts b/webapp/channels/src/components/common/hooks/usePreference.ts index 5361d4c19f..968303739e 100644 --- a/webapp/channels/src/components/common/hooks/usePreference.ts +++ b/webapp/channels/src/components/common/hooks/usePreference.ts @@ -8,18 +8,16 @@ import type {PreferenceType} from '@mattermost/types/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common'; -import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences'; +import {get as getPreference} from 'mattermost-redux/selectors/entities/preferences'; import type {ActionResult} from 'mattermost-redux/types/actions'; -import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; + +import type {GlobalState} from 'types/store'; export default function usePreference(category: string, name: string): [string | undefined, (value: string) => Promise] { const dispatch = useDispatch(); const userId = useSelector(getCurrentUserId); - const preferences = useSelector(getMyPreferences); - - const key = getPreferenceKey(category, name); - const preference = preferences[key]; + const preferenceValue = useSelector((state: GlobalState) => getPreference(state, category, name)); const setPreference = useCallback((value: string) => { const preference: PreferenceType = { @@ -31,5 +29,5 @@ export default function usePreference(category: string, name: string): [string | return dispatch(savePreferences(userId, [preference])); }, [category, name, userId]); - return useMemo(() => ([preference?.value, setPreference]), [preference?.value, setPreference]); + return useMemo(() => ([preferenceValue, setPreference]), [preferenceValue, setPreference]); } diff --git a/webapp/channels/src/components/invitation_modal/overage_users_banner_notice/index.tsx b/webapp/channels/src/components/invitation_modal/overage_users_banner_notice/index.tsx index a8baeb2e95..e070e65cfa 100644 --- a/webapp/channels/src/components/invitation_modal/overage_users_banner_notice/index.tsx +++ b/webapp/channels/src/components/invitation_modal/overage_users_banner_notice/index.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React from 'react'; import {FormattedMessage} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; @@ -10,7 +10,7 @@ import type {PreferenceType} from '@mattermost/types/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; import {getLicense} from 'mattermost-redux/selectors/entities/general'; -import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; +import {getOverageBannerPreferences} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users'; import AlertBanner from 'components/alert_banner'; @@ -42,9 +42,8 @@ const OverageUsersBannerNotice = () => { const isGovSku = getIsGovSku(license); const seatsPurchased = parseInt(license.Users, 10); const isCloud = useSelector(isCurrentLicenseCloud); - const getPreferencesCategory = useMemo(makeGetCategory, []); const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); - const overagePreferences = useSelector((state: GlobalState) => getPreferencesCategory(state, Preferences.OVERAGE_USERS_BANNER)); + const overagePreferences = useSelector(getOverageBannerPreferences); const activeUsers = ((stats || {})[StatTypes.TOTAL_USERS]) as number || 0; const { diff --git a/webapp/channels/src/components/onboarding_tasks/onboarding_tasks_manager.tsx b/webapp/channels/src/components/onboarding_tasks/onboarding_tasks_manager.tsx index 6853e2bf54..3cca6f7f4e 100644 --- a/webapp/channels/src/components/onboarding_tasks/onboarding_tasks_manager.tsx +++ b/webapp/channels/src/components/onboarding_tasks/onboarding_tasks_manager.tsx @@ -9,7 +9,6 @@ import {matchPath, useLocation} from 'react-router-dom'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common'; import {getLicense} from 'mattermost-redux/selectors/entities/general'; -import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; import {isCurrentUserGuestUser, isCurrentUserSystemAdmin, isFirstAdmin} from 'mattermost-redux/selectors/entities/users'; import {trackEvent as trackEventAction} from 'actions/telemetry_actions'; @@ -22,6 +21,7 @@ import { } from 'actions/views/onboarding_tasks'; import {setProductMenuSwitcherOpen} from 'actions/views/product_menu'; import {setStatusDropdown} from 'actions/views/status_dropdown'; +import {getOnboardingTaskPreferences} from 'selectors/onboarding'; import Channels from 'components/common/svg_images_components/channels_svg'; import Gears from 'components/common/svg_images_components/gears_svg'; @@ -46,8 +46,6 @@ import type {GlobalState} from 'types/store'; import {OnboardingTaskCategory, OnboardingTaskList, OnboardingTasksName, TaskNameMapToSteps} from './constants'; import {generateTelemetryTag} from './utils'; -const getCategory = makeGetCategory(); - const useGetTaskDetails = () => { const {formatMessage} = useIntl(); return { @@ -145,7 +143,7 @@ export const useTasksList = () => { }; export const useTasksListWithStatus = () => { - const dataInDb = useSelector((state: GlobalState) => getCategory(state, OnboardingTaskCategory)); + const dataInDb = useSelector(getOnboardingTaskPreferences); const tasksList = useTasksList(); const getTaskDetails = useGetTaskDetails(); return useMemo(() => diff --git a/webapp/channels/src/components/post/index.tsx b/webapp/channels/src/components/post/index.tsx index 05f4ab7dbc..12959d4ce3 100644 --- a/webapp/channels/src/components/post/index.tsx +++ b/webapp/channels/src/components/post/index.tsx @@ -4,16 +4,15 @@ import {connect} from 'react-redux'; import type {ConnectedProps} from 'react-redux'; import {bindActionCreators} from 'redux'; -import type {AnyAction, Dispatch} from 'redux'; +import type {Dispatch} from 'redux'; import type {Emoji} from '@mattermost/types/emojis'; import type {Post} from '@mattermost/types/posts'; -import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences'; import {General} from 'mattermost-redux/constants'; import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled} from 'mattermost-redux/selectors/entities/posts'; +import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, isPostFlagged} from 'mattermost-redux/selectors/entities/posts'; import type {UserActivityPost} from 'mattermost-redux/selectors/entities/posts'; import { get, @@ -179,7 +178,7 @@ function makeMapStateToProps() { channelIsArchived: isArchivedChannel(channel), isConsecutivePost: isConsecutivePost(state, ownProps), previousPostIsComment, - isFlagged: get(state, Preferences.CATEGORY_FLAGGED_POST, post.id, null) !== null, + isFlagged: isPostFlagged(state, post.id), compactDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT, colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT) === 'true', shouldShowActionsMenu: shouldShowActionsMenu(state, post), @@ -220,12 +219,11 @@ function makeMapStateToProps() { }; } -function mapDispatchToProps(dispatch: Dispatch) { +function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ markPostAsUnread, emitShortcutReactToLastPostFrom, - setActionsMenuInitialisationState, selectPost, selectPostFromRightHandSideSearch, setRhsExpanded, diff --git a/webapp/channels/src/components/post/post_component.test.tsx b/webapp/channels/src/components/post/post_component.test.tsx index 0deeaee63b..c634ea1712 100644 --- a/webapp/channels/src/components/post/post_component.test.tsx +++ b/webapp/channels/src/components/post/post_component.test.tsx @@ -41,7 +41,6 @@ describe('PostComponent', () => { actions: { markPostAsUnread: jest.fn(), emitShortcutReactToLastPostFrom: jest.fn(), - setActionsMenuInitialisationState: jest.fn(), selectPost: jest.fn(), selectPostFromRightHandSideSearch: jest.fn(), removePost: jest.fn(), diff --git a/webapp/channels/src/components/post/post_component.tsx b/webapp/channels/src/components/post/post_component.tsx index f54763b77e..3c037451eb 100644 --- a/webapp/channels/src/components/post/post_component.tsx +++ b/webapp/channels/src/components/post/post_component.tsx @@ -91,7 +91,6 @@ export type Props = { actions: { markPostAsUnread: (post: Post, location: string) => void; emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void; - setActionsMenuInitialisationState: (viewed: Record) => void; selectPost: (post: Post) => void; selectPostFromRightHandSideSearch: (post: Post) => void; removePost: (post: Post) => void; diff --git a/webapp/channels/src/components/post_view/combined_system_message/index.ts b/webapp/channels/src/components/post_view/combined_system_message/index.ts index c730873191..a81efb0fee 100644 --- a/webapp/channels/src/components/post_view/combined_system_message/index.ts +++ b/webapp/channels/src/components/post_view/combined_system_message/index.ts @@ -8,8 +8,7 @@ import type {Dispatch} from 'redux'; import type {GlobalState} from '@mattermost/types/store'; import {getMissingProfilesByIds, getMissingProfilesByUsernames} from 'mattermost-redux/actions/users'; -import {Preferences} from 'mattermost-redux/constants'; -import {getBool} from 'mattermost-redux/selectors/entities/preferences'; +import {shouldShowJoinLeaveMessages} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUser, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users'; import CombinedSystemMessage from './combined_system_message'; @@ -29,7 +28,7 @@ function makeMapStateToProps() { return { currentUserId: currentUser.id, currentUsername: currentUser.username, - showJoinLeave: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true), + showJoinLeave: shouldShowJoinLeaveMessages(state), userProfiles: getProfilesByIdsAndUsernames(state, {allUserIds, allUsernames}), }; }; diff --git a/webapp/channels/src/components/system_notice/index.ts b/webapp/channels/src/components/system_notice/index.ts index bb96b687c9..ab90533849 100644 --- a/webapp/channels/src/components/system_notice/index.ts +++ b/webapp/channels/src/components/system_notice/index.ts @@ -25,39 +25,36 @@ import {Preferences} from 'utils/constants'; import type {GlobalState} from 'types/store'; -function makeMapStateToProps() { - const getCategory = makeGetCategory(); +const getSystemNoticePreferences = makeGetCategory('getSystemNoticePreferences', Preferences.CATEGORY_SYSTEM_NOTICE); +const getPreferenceNameMap = createSelector( + 'getPreferenceNameMap', + getSystemNoticePreferences, + (preferences) => { + const nameMap: {[key: string]: PreferenceType} = {}; + preferences.forEach((p) => { + nameMap[p.name] = p; + }); + return nameMap; + }, +); - const getPreferenceNameMap = createSelector( - 'getPreferenceNameMap', - getCategory, - (preferences) => { - const nameMap: {[key: string]: PreferenceType} = {}; - preferences.forEach((p) => { - nameMap[p.name] = p; - }); - return nameMap; - }, - ); +function mapStateToProps(state: GlobalState) { + const license = getLicense(state); + const config = getConfig(state); + const serverVersion = state.entities.general.serverVersion; + const analytics = state.entities.admin.analytics; - return function mapStateToProps(state: GlobalState) { - const license = getLicense(state); - const config = getConfig(state); - const serverVersion = state.entities.general.serverVersion; - const analytics = state.entities.admin.analytics; - - return { - currentUserId: state.entities.users.currentUserId, - preferences: getPreferenceNameMap(state, Preferences.CATEGORY_SYSTEM_NOTICE), - dismissedNotices: state.views.notice.hasBeenDismissed, - isSystemAdmin: haveISystemPermission(state, {permission: Permissions.MANAGE_SYSTEM}), - notices: Notices, - config, - license, - serverVersion, - analytics, - currentChannel: getCurrentChannel(state), - }; + return { + currentUserId: state.entities.users.currentUserId, + preferences: getPreferenceNameMap(state), + dismissedNotices: state.views.notice.hasBeenDismissed, + isSystemAdmin: haveISystemPermission(state, {permission: Permissions.MANAGE_SYSTEM}), + notices: Notices, + config, + license, + serverVersion, + analytics, + currentChannel: getCurrentChannel(state), }; } @@ -71,4 +68,4 @@ function mapDispatchToProps(dispatch: Dispatch) { }; } -export default connect(makeMapStateToProps, mapDispatchToProps)(SystemNotice); +export default connect(mapStateToProps, mapDispatchToProps)(SystemNotice); diff --git a/webapp/channels/src/components/threading/global_threads/thread_menu/thread_menu.tsx b/webapp/channels/src/components/threading/global_threads/thread_menu/thread_menu.tsx index 5097123976..472df227e6 100644 --- a/webapp/channels/src/components/threading/global_threads/thread_menu/thread_menu.tsx +++ b/webapp/channels/src/components/threading/global_threads/thread_menu/thread_menu.tsx @@ -4,13 +4,12 @@ import React, {memo, useCallback} from 'react'; import type {ReactNode} from 'react'; import {useIntl} from 'react-intl'; -import {useDispatch, useSelector, shallowEqual} from 'react-redux'; +import {useDispatch, useSelector} from 'react-redux'; import type {UserThread} from '@mattermost/types/threads'; import {setThreadFollow, updateThreadRead, markLastPostInThreadAsUnread} from 'mattermost-redux/actions/threads'; -import {Preferences} from 'mattermost-redux/constants'; -import {get} from 'mattermost-redux/selectors/entities/preferences'; +import {isPostFlagged} from 'mattermost-redux/selectors/entities/posts'; import { flagPost as savePost, @@ -56,7 +55,7 @@ function ThreadMenu({ goToInChannel, } = useThreadRouting(); - const isSaved = useSelector((state: GlobalState) => get(state, Preferences.CATEGORY_FLAGGED_POST, threadId, null) != null, shallowEqual); + const isSaved = useSelector((state: GlobalState) => isPostFlagged(state, threadId)); const handleReadUnread = useCallback(() => { const lastViewedAt = hasUnreads ? Date.now() : unreadTimestamp; diff --git a/webapp/channels/src/components/user_settings/advanced/index.ts b/webapp/channels/src/components/user_settings/advanced/index.ts index 6a523c556a..bf06d17197 100644 --- a/webapp/channels/src/components/user_settings/advanced/index.ts +++ b/webapp/channels/src/components/user_settings/advanced/index.ts @@ -24,8 +24,10 @@ import type {GlobalState} from 'types/store'; import AdvancedSettingsDisplay from './user_settings_advanced'; import type {OwnProps} from './user_settings_advanced'; -function makeMapStateToProps(state: GlobalState, props: OwnProps) { - const getAdvancedSettingsCategory = props.adminMode ? makeGetUserCategory(props.user.id) : makeGetCategory(); +const getAdvancedSettingsCategory = makeGetCategory('getAdvancedSettingsCategory', Preferences.CATEGORY_ADVANCED_SETTINGS); + +function makeMapStateToProps() { + const getUserAdvancedSettingsCategory = makeGetUserCategory('getAdvancedSettingsCategory', Preferences.CATEGORY_ADVANCED_SETTINGS); return (state: GlobalState, props: OwnProps) => { const config = getConfig(state); @@ -34,9 +36,10 @@ function makeMapStateToProps(state: GlobalState, props: OwnProps) { const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true'; const userPreferences = props.adminMode && props.userPreferences ? props.userPreferences : undefined; + const advancedSettingsCategory = userPreferences ? getUserAdvancedSettingsCategory(state, props.user.id) : getAdvancedSettingsCategory(state); return { - advancedSettingsCategory: getAdvancedSettingsCategory(state, Preferences.CATEGORY_ADVANCED_SETTINGS), + advancedSettingsCategory, sendOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', 'false', userPreferences), codeBlockOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', 'true', userPreferences), formatting: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true', userPreferences), diff --git a/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.tsx b/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.tsx index 5841783798..a3c374fe5e 100644 --- a/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.tsx +++ b/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.tsx @@ -25,7 +25,7 @@ export type OwnProps = { type Props = OwnProps & { active: boolean; areAllSectionsInactive: boolean; - joinLeave?: string; + joinLeave: string; onUpdateSection: (section?: string) => void; renderOnOffLabel: (label: string) => ReactNode; actions: { @@ -34,7 +34,7 @@ type Props = OwnProps & { } type State = { - joinLeaveState?: string; + joinLeaveState: string; isSaving?: boolean; serverError?: string; } diff --git a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx index ec293e60b8..0a19ec8996 100644 --- a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx +++ b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx @@ -108,7 +108,7 @@ export default class AdvancedSettingsDisplay extends React.PureComponent { - return { - currentTeamId: getCurrentTeamId(state), - theme: getTheme(state), - applyToAllTeams: getThemeCategory(state, Preferences.CATEGORY_THEME).length <= 1, - showAllTeamsCheckbox: getMyTeamsCount(state) > 1, - }; +function mapStateToProps(state: GlobalState) { + return { + currentTeamId: getCurrentTeamId(state), + theme: getTheme(state), + applyToAllTeams: getThemePreferences(state).length <= 1, + showAllTeamsCheckbox: getMyTeamsCount(state) > 1, }; } @@ -40,4 +34,4 @@ function mapDispatchToProps(dispatch: Dispatch) { }; } -export default connect(makeMapStateToProps, mapDispatchToProps)(UserSettingsTheme); +export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsTheme); diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.test.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.test.ts index 69fd0485eb..0685d49fdf 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.test.ts @@ -14,8 +14,8 @@ import {createCustomEmoji} from 'mattermost-redux/actions/emojis'; import * as Actions from 'mattermost-redux/actions/posts'; import {loadMe} from 'mattermost-redux/actions/users'; import {Client4} from 'mattermost-redux/client'; +import {isPostFlagged} from 'mattermost-redux/selectors/entities/posts'; import type {GetStateFunc} from 'mattermost-redux/types/actions'; -import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; import mockStore from 'tests/test_store'; @@ -1038,10 +1038,8 @@ describe('Actions.Posts', () => { reply(200, OK_RESPONSE); dispatch(Actions.flagPost(post1.id)); - const state = getState(); - const prefKey = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, post1.id); - const preference = state.entities.preferences.myPreferences[prefKey]; - expect(preference).toBeTruthy(); + + expect(isPostFlagged(getState(), post1.id)).toBe(true); }); it('unflagPost', async () => { @@ -1069,20 +1067,15 @@ describe('Actions.Posts', () => { put(`/${TestHelper.basicUser!.id}/preferences`). reply(200, OK_RESPONSE); dispatch(Actions.flagPost(post1.id)); - let state = getState(); - const prefKey = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, post1.id); - const preference = state.entities.preferences.myPreferences[prefKey]; - expect(preference).toBeTruthy(); + + expect(isPostFlagged(getState(), post1.id)).toBe(true); nock(Client4.getUsersRoute()). delete(`/${TestHelper.basicUser!.id}/preferences`). reply(200, OK_RESPONSE); dispatch(Actions.unflagPost(post1.id)); - state = getState(); - const unflagged = state.entities.preferences.myPreferences[prefKey]; - if (unflagged) { - throw new Error('unexpected unflagged'); - } + + expect(isPostFlagged(getState(), post1.id)).toBe(false); }); it('setUnreadPost', async () => { diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts index 4d4d47b236..c796e66fb1 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts @@ -1234,6 +1234,7 @@ export function unflagPost(postId: string): ActionFuncAsync { user_id: currentUserId, category: Preferences.CATEGORY_FLAGGED_POST, name: postId, + value: 'true', }; Client4.trackEvent('action', 'action_posts_unflag'); diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts index 7c3207f968..413bfbed81 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts @@ -5,7 +5,7 @@ import type {PreferenceType} from '@mattermost/types/preferences'; import {PreferenceTypes} from 'mattermost-redux/action_types'; import {Client4} from 'mattermost-redux/client'; -import {getMyPreferences as getMyPreferencesSelector, makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; +import {getMyPreferences as getMyPreferencesSelector, getThemePreferences} from 'mattermost-redux/selectors/entities/preferences'; import type {Theme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import type {ActionFuncAsync, ThunkActionFunc} from 'mattermost-redux/types/actions'; @@ -56,20 +56,6 @@ export function getUserPreferences(userID: string) { }); } -export function setActionsMenuInitialisationState(initializationState: Record): ThunkActionFunc { - return async (dispatch, getState) => { - const state = getState(); - const currentUserId = getCurrentUserId(state); - const preference: PreferenceType = { - user_id: currentUserId, - category: Preferences.CATEGORY_ACTIONS_MENU, - name: Preferences.NAME_ACTIONS_MENU_TUTORIAL_STATE, - value: JSON.stringify(initializationState), - }; - await dispatch(savePreferences(currentUserId, [preference])); - }; -} - export function setCustomStatusInitialisationState(initializationState: Record): ThunkActionFunc { return async (dispatch, getState) => { const state = getState(); @@ -130,7 +116,7 @@ export function deleteTeamSpecificThemes(): ActionFuncAsync { return async (dispatch, getState) => { const state = getState(); - const themePreferences: PreferenceType[] = makeGetCategory()(state, Preferences.CATEGORY_THEME); + const themePreferences: PreferenceType[] = getThemePreferences(state); const currentUserId = getCurrentUserId(state); const toDelete = themePreferences.filter((pref) => pref.name !== ''); diff --git a/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts index 1f71938371..01d391dccf 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts @@ -37,10 +37,6 @@ const Preferences = { MENTION_KEYS: 'mention_keys', USE_MILITARY_TIME: 'use_military_time', - CATEGORY_ACTIONS_MENU: 'actions_menu', - NAME_ACTIONS_MENU_TUTORIAL_STATE: 'actions_menu_tutorial_state', - ACTIONS_MENU_VIEWED: 'actions_menu_modal_viewed', - CATEGORY_CUSTOM_STATUS: 'custom_status', NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state', NAME_RECENT_CUSTOM_STATUSES: 'recent_custom_statuses', @@ -82,6 +78,8 @@ const Preferences = { HIDE_BATCH_EXPORT_CONFIRM_MODAL: 'hide_batch_export_confirm_modal', HIDE_MYSQL_STATS_NOTIFICATION: 'hide_mysql_stats_notifcation', + CATEGORY_OVERAGE_USERS_BANNER: 'overage_users_banner', + CATEGORY_THEME: 'theme', THEMES: { denim: { diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.test.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.test.ts index a747150374..ce0a810810 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.test.ts @@ -253,6 +253,11 @@ describe('Selectors.Posts', () => { const testStateAny = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: userAny.id, profiles: profilesAny, @@ -372,6 +377,11 @@ describe('Selectors.Posts', () => { const testStateRoot = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: userRoot.id, profiles: profilesRoot, @@ -485,6 +495,11 @@ describe('Selectors.Posts', () => { const testStateNever = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: userNever.id, profiles: profilesNever, @@ -601,6 +616,11 @@ describe('Selectors.Posts', () => { const testStateAny = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: userAny.id, profiles: profilesAny, @@ -678,6 +698,11 @@ describe('Selectors.Posts', () => { const testStateAny = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: userAny.id, profiles: profilesAny, @@ -1823,6 +1848,11 @@ describe('Selectors.Posts', () => { }; const state = { entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: testPosts, postsInChannel: { @@ -1852,6 +1882,11 @@ describe('Selectors.Posts', () => { }; const state = { entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: testPosts, postsInChannel: { @@ -1880,6 +1915,11 @@ describe('Selectors.Posts', () => { channels: { currentChannelId: 'abcd', }, + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: {}, postsInChannel: [], @@ -1910,6 +1950,11 @@ describe('Selectors.Posts', () => { channels: { currentChannelId: 'abcd', }, + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: postsAny, postsInChannel: { @@ -2147,6 +2192,11 @@ describe('getPostsInCurrentChannel', () => { channels: { currentChannelId: 'channel1', }, + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: {}, postsInChannel: {}, @@ -2177,6 +2227,11 @@ describe('getPostsInCurrentChannel', () => { channels: { currentChannelId: 'channel1', }, + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: { post1, @@ -2214,6 +2269,11 @@ describe('getPostsInCurrentChannel', () => { channels: { currentChannelId: 'channel1', }, + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: { post1, diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.ts index 79bffaef18..cbdbb0aabf 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/posts.ts @@ -24,11 +24,10 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentChannelId, getCurrentUser} from 'mattermost-redux/selectors/entities/common'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences'; +import {getBool, shouldShowJoinLeaveMessages} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getUsers, getCurrentUserId, getUserStatuses} from 'mattermost-redux/selectors/entities/users'; import {createIdsSelector} from 'mattermost-redux/utils/helpers'; -import {shouldShowJoinLeaveMessages} from 'mattermost-redux/utils/post_list'; import { isPostEphemeral, isSystemMessage, @@ -37,7 +36,6 @@ import { isPostPendingOrFailed, isPostCommentMention, } from 'mattermost-redux/utils/post_utils'; -import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; import {isGuest} from 'mattermost-redux/utils/user_utils'; export function getAllPosts(state: GlobalState) { @@ -53,6 +51,10 @@ export function getPost(state: GlobalState, postId: Post['id']): Post { return getAllPosts(state)[postId]; } +export function isPostFlagged(state: GlobalState, postId: Post['id']): boolean { + return getBool(state, Preferences.CATEGORY_FLAGGED_POST, postId); +} + export function getPostRepliesCount(state: GlobalState, postId: Post['id']): number { return state.entities.posts.postsReplies[postId] || 0; } @@ -344,18 +346,15 @@ export function makeGetPostsInChannel(): (state: GlobalState, channelId: Channel getPostsInThread, (state: GlobalState, channelId: Channel['id']) => getPostIdsInChannel(state, channelId), getCurrentUser, - getMyPreferences, + shouldShowJoinLeaveMessages, (state: GlobalState, channelId: Channel['id'], numPosts: number) => numPosts || Posts.POST_CHUNK_SIZE, - (allPosts, postsInThread, allPostIds, currentUser, myPreferences, numPosts) => { + (allPosts, postsInThread, allPostIds, currentUser, showJoinLeave, numPosts) => { if (!allPostIds) { return null; } const posts: PostWithFormatData[] = []; - const joinLeavePref = myPreferences[getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]; - const showJoinLeave = joinLeavePref ? joinLeavePref.value !== 'false' : true; - const postIds = numPosts === -1 ? allPostIds : allPostIds.slice(0, numPosts); for (let i = 0; i < postIds.length; i++) { @@ -388,15 +387,13 @@ export function makeGetPostsAroundPost(): (state: GlobalState, postId: Post['id' getPostsInThread, (state: GlobalState, focusedPostId) => focusedPostId, getCurrentUser, - getMyPreferences, - (postIds, allPosts, postsInThread, focusedPostId, currentUser, myPreferences) => { + shouldShowJoinLeaveMessages, + (postIds, allPosts, postsInThread, focusedPostId, currentUser, showJoinLeave) => { if (!postIds || !currentUser) { return null; } const posts: PostWithFormatData[] = []; - const joinLeavePref = myPreferences[getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]; - const showJoinLeave = joinLeavePref ? joinLeavePref.value !== 'false' : true; for (let i = 0; i < postIds.length; i++) { const post = allPosts[postIds[i]]; @@ -565,13 +562,11 @@ export const getMostRecentPostIdInChannel: (state: GlobalState, channelId: Chann 'getMostRecentPostIdInChannel', getAllPosts, (state: GlobalState, channelId: string) => getPostIdsInChannel(state, channelId), - getMyPreferences, - (posts, postIdsInChannel, preferences) => { + shouldShowJoinLeaveMessages, + (posts, postIdsInChannel, allowSystemMessages) => { if (!postIdsInChannel) { return ''; } - const key = getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE); - const allowSystemMessages = preferences[key] ? preferences[key].value === 'true' : true; if (!allowSystemMessages) { // return the most recent non-system message in the channel diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.test.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.test.ts index c9d8268cd8..6e977ea1d3 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.test.ts @@ -135,11 +135,6 @@ describe('Selectors.Preferences', () => { }); }); - it('get preferences by category', () => { - const getCategory = Selectors.makeGetCategory(); - expect(getCategory(testState, category1)).toEqual([pref1]); - }); - it('get direct channel show preferences', () => { expect(Selectors.getDirectShowPreferences(testState)).toEqual([dmPref1, dmPref2]); }); @@ -599,6 +594,233 @@ describe('Selectors.Preferences', () => { }); }); +describe('makeGetCategory', () => { + const category1 = 'category1'; + const category2 = 'category2'; + const name1 = 'name1'; + const name2 = 'name2'; + + function getBaseState() { + return deepFreezeAndThrowOnMutation({ + entities: { + preferences: { + myPreferences: { + [getPreferenceKey(category1, name1)]: { + category: category1, + name: name1, + value: 'value1', + }, + [getPreferenceKey(category2, name1)]: { + category: category2, + name: name1, + value: 'value2', + }, + [getPreferenceKey(category1, name2)]: { + category: category1, + name: name2, + value: 'value3', + }, + }, + }, + }, + }) as GlobalState; + } + + it('should return preferences in a category', () => { + const state = getBaseState(); + + const getCategory1 = Selectors.makeGetCategory('getCategory1', category1); + const getCategory2 = Selectors.makeGetCategory('getCategory2', category2); + + expect(getCategory1(state)).toEqual([ + { + category: category1, + name: name1, + value: 'value1', + }, + { + category: category1, + name: name2, + value: 'value3', + }, + ]); + expect(getCategory2(state)).toEqual([ + { + category: category2, + name: name1, + value: 'value2', + }, + ]); + }); + + it('should return the same preference objects unless they change', () => { + let state = getBaseState(); + + const preference1 = state.entities.preferences.myPreferences[getPreferenceKey(category1, name1)]; + const preference2 = state.entities.preferences.myPreferences[getPreferenceKey(category1, name2)]; + + const getCategory1 = Selectors.makeGetCategory('getCategory1', category1); + + expect(getCategory1(state)[0]).toBe(preference1); + expect(getCategory1(state)[1]).toBe(preference2); + + state = mergeObjects(state, { + entities: { + preferences: { + myPreferences: { + [getPreferenceKey(category1, name1)]: { + category: category1, + name: name1, + value: 'new value', + }, + }, + }, + }, + }); + + expect(getCategory1(state)[0]).not.toBe(preference1); + expect(getCategory1(state)[1]).toBe(preference2); + }); + + it('should only return a new array when one of the preferences in that category changes', () => { + let state = getBaseState(); + + const getCategory1 = Selectors.makeGetCategory('getCategory1', category1); + const getCategory2 = Selectors.makeGetCategory('getCategory2', category2); + + const originalResult1 = getCategory1(state); + const originalResult2 = getCategory2(state); + + expect(getCategory1(state)).toBe(originalResult1); + + state = mergeObjects(state, { + entities: { + preferences: { + myPreferences: { + [getPreferenceKey(category2, name2)]: { + category: category2, + name: name2, + value: 'value4', + }, + }, + }, + }, + }); + + expect(getCategory1(state)).toBe(originalResult1); + expect(getCategory2(state)).not.toBe(originalResult2); + + state = mergeObjects(state, { + entities: { + preferences: { + myPreferences: { + [getPreferenceKey(category1, name1)]: { + category: category1, + name: name1, + value: 'new value', + }, + }, + }, + }, + }); + + expect(getCategory1(state)).not.toBe(originalResult1); + expect(getCategory2(state)).not.toBe(originalResult2); + }); +}); + +describe('shouldShowJoinLeaveMessages', () => { + it('should default to true', () => { + const state = { + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, + preferences: { + myPreferences: {}, + }, + }, + } as unknown as GlobalState; + + // Defaults to show post + const show = Selectors.shouldShowJoinLeaveMessages(state); + expect(show).toEqual(true); + }); + + it('set config to false, return false', () => { + const state = { + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'false', + }, + }, + preferences: { + myPreferences: {}, + }, + }, + } as unknown as GlobalState; + + // Defaults to show post + const show = Selectors.shouldShowJoinLeaveMessages(state); + expect(show).toEqual(false); + }); + + it('if user preference, set default wont be used', () => { + const state = { + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'false', + }, + }, + preferences: { + myPreferences: { + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'true', + }, + + }, + }, + }, + } as unknown as GlobalState; + + // Defaults to show post + const show = Selectors.shouldShowJoinLeaveMessages(state); + expect(show).toEqual(true); + }); + + it('if user preference, set default wont be used', () => { + const state = { + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, + preferences: { + myPreferences: { + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'false', + }, + + }, + }, + }, + } as unknown as GlobalState; + + // Defaults to show post + const show = Selectors.shouldShowJoinLeaveMessages(state); + expect(show).toEqual(false); + }); +}); + describe('shouldShowUnreadsCategory', () => { test('should return value from the preference if set', () => { const state = { diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts index cd40097722..8e4b6c4a21 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts @@ -8,7 +8,7 @@ import type {GlobalState} from '@mattermost/types/store'; import {General, Preferences} from 'mattermost-redux/constants'; import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {getConfig, getFeatureFlagValue, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {createShallowSelector} from 'mattermost-redux/utils/helpers'; +import {createIdsSelector, createShallowSelector} from 'mattermost-redux/utils/helpers'; import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; import {setThemeDefaults} from 'mattermost-redux/utils/theme_utils'; @@ -20,19 +20,18 @@ export function getUserPreferences(state: GlobalState, userID: string): Preferen return state.entities.preferences.userPreferences[userID]; } -export function get(state: GlobalState, category: string, name: string, defaultValue: any = '', preferences?: PreferencesType) { +function getPreferenceObject(state: GlobalState, category: string, name: string): PreferenceType | undefined { + return getMyPreferences(state)[getPreferenceKey(category, name)]; +} + +export function get(state: GlobalState, category: string, name: string, defaultValue = '', preferences?: PreferencesType): string { if (preferences) { return getFromPreferences(preferences, category, name, defaultValue); } - const key = getPreferenceKey(category, name); - const prefs = getMyPreferences(state); + const pref = getPreferenceObject(state, category, name); - if (!(key in prefs)) { - return defaultValue; - } - - return prefs[key].value; + return pref ? pref.value : defaultValue; } export function getFromPreferences(preferences: PreferencesType, category: string, name: string, defaultValue: any = '') { @@ -51,16 +50,15 @@ export function getBool(state: GlobalState, category: string, name: string, defa } export function getInt(state: GlobalState, category: string, name: string, defaultValue = 0, userPreferences?: PreferencesType): number { - const value = get(state, category, name, defaultValue, userPreferences); + const value = get(state, category, name, String(defaultValue), userPreferences); return parseInt(value, 10); } -export function makeGetCategory(): (state: GlobalState, category: string) => PreferenceType[] { - return createSelector( - 'makeGetCategory', +export function makeGetCategory(selectorName: string, category: string): (state: GlobalState) => PreferenceType[] { + return createIdsSelector( + selectorName, getMyPreferences, - (state: GlobalState, category: string) => category, - (preferences, category) => { + (preferences) => { const prefix = category + '--'; const prefsInCategory: PreferenceType[] = []; @@ -75,12 +73,11 @@ export function makeGetCategory(): (state: GlobalState, category: string) => Pre ); } -export function makeGetUserCategory(userID: string): (state: GlobalState, category: string) => PreferenceType[] { - return createSelector( - 'makeGetCategory', - (state) => getUserPreferences(state, userID), - (state: GlobalState, category: string) => category, - (preferences, category) => { +export function makeGetUserCategory(selectorName: string, category: string): (state: GlobalState, userID: string) => PreferenceType[] { + return createIdsSelector( + selectorName, + (state: GlobalState, userID: string) => getUserPreferences(state, userID), + (preferences) => { const prefix = category + '--'; const prefsInCategory: PreferenceType[] = []; @@ -95,28 +92,18 @@ export function makeGetUserCategory(userID: string): (state: GlobalState, catego ); } -const getDirectShowCategory = makeGetCategory(); - -export function getDirectShowPreferences(state: GlobalState) { - return getDirectShowCategory(state, Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); -} - -const getGroupShowCategory = makeGetCategory(); - -export function getGroupShowPreferences(state: GlobalState) { - return getGroupShowCategory(state, Preferences.CATEGORY_GROUP_CHANNEL_SHOW); -} +export const getDirectShowPreferences = makeGetCategory('getDirectShowPreferences', Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); +export const getGroupShowPreferences = makeGetCategory('getGroupShowPreferences', Preferences.CATEGORY_GROUP_CHANNEL_SHOW); export const getTeammateNameDisplaySetting: (state: GlobalState) => string = createSelector( 'getTeammateNameDisplaySetting', getConfig, - getMyPreferences, + (state) => getPreferenceObject(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT), getLicense, - (config, preferences, license) => { + (config, teammateNameDisplayPreference, license) => { const useAdminTeammateNameDisplaySetting = (license && license.LockTeammateNameDisplay === 'true') && config.LockTeammateNameDisplay === 'true'; - const key = getPreferenceKey(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT); - if (preferences[key] && !useAdminTeammateNameDisplaySetting) { - return preferences[key].value || ''; + if (teammateNameDisplayPreference && !useAdminTeammateNameDisplaySetting) { + return teammateNameDisplayPreference.value || ''; } else if (config.TeammateNameDisplay) { return config.TeammateNameDisplay; } @@ -124,6 +111,8 @@ export const getTeammateNameDisplaySetting: (state: GlobalState) => string = cre }, ); +export const getThemePreferences = makeGetCategory('getThemePreferences', Preferences.CATEGORY_THEME); + const getThemePreference = createSelector( 'getThemePreference', getMyPreferences, @@ -218,6 +207,14 @@ export function makeGetStyleFromTheme