Various improvements to preferences code (#27710)

* Add isPostFlagged selector

* Remove unused code for actions_menu preferences

* Improve types of Preference and getPreference

* MM-58111 Change makeGetCategory to take category name once

* Use shouldShowJoinLeaveMessages everywhere

* Change makeGetCategory to better memoize its result

* Fix test that needs EnableJoinLeaveMessageByDefault to be set

* Remove more references to action_menu preferences
This commit is contained in:
Harrison Healey 2024-08-01 18:53:30 -04:00 committed by GitHub
parent 28939d84da
commit 6956923b6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 446 additions and 392 deletions

View File

@ -126,18 +126,6 @@ declare namespace Cypress {
*/ */
apiSaveCloudTrialBannerPreference(userId: string, name: string, value: string): Chainable<Response>; apiSaveCloudTrialBannerPreference(userId: string, name: string, value: string): Chainable<Response>;
/**
* 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<Response>;
/** /**
* Save show trial modal. * Save show trial modal.
* See https://api.mattermost.com/#tag/preferences/paths/~1users~1{user_id}~1preferences/put * See https://api.mattermost.com/#tag/preferences/paths/~1users~1{user_id}~1preferences/put

View File

@ -256,17 +256,6 @@ Cypress.Commands.add('apiSaveCloudTrialBannerPreference', (userId, name, value)
return cy.apiSaveUserPreference([preference], userId); 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') => { Cypress.Commands.add('apiSaveStartTrialModal', (userId, value = 'true') => {
const preference = { const preference = {
user_id: userId, user_id: userId,
@ -402,12 +391,6 @@ Cypress.Commands.add('apiDisableTutorials', (userId) => {
name: userId, name: userId,
value: '999', value: '999',
}, },
{
user_id: userId,
category: 'actions_menu',
name: 'actions_menu_tutorial_state',
value: '{"actions_menu_modal_viewed":true}',
},
{ {
user_id: userId, user_id: userId,
category: 'drafts', category: 'drafts',

View File

@ -234,7 +234,6 @@ Cypress.Commands.add('apiCreateUser', ({
prefix = 'user', prefix = 'user',
createAt = 0, createAt = 0,
bypassTutorial = true, bypassTutorial = true,
hideActionsMenu = true,
hideOnboarding = true, hideOnboarding = true,
bypassWhatsNewModal = true, bypassWhatsNewModal = true,
user = null, user = null,
@ -265,10 +264,6 @@ Cypress.Commands.add('apiCreateUser', ({
cy.apiDisableTutorials(createdUser.id); cy.apiDisableTutorials(createdUser.id);
} }
if (hideActionsMenu) {
cy.apiSaveActionsMenuPreference(createdUser.id, true);
}
if (hideOnboarding) { if (hideOnboarding) {
cy.apiSaveOnboardingPreference(createdUser.id, 'hide', 'true'); cy.apiSaveOnboardingPreference(createdUser.id, 'hide', 'true');
cy.apiSaveOnboardingPreference(createdUser.id, 'skip', 'true'); cy.apiSaveOnboardingPreference(createdUser.id, 'skip', 'true');

View File

@ -261,7 +261,6 @@ function resetUserPreference(userId) {
cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_open', 'false'); cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_open', 'false');
cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_show', 'false'); cy.apiSaveOnboardingTaskListPreference(userId, 'onboarding_task_list_show', 'false');
cy.apiSaveCloudTrialBannerPreference(userId, 'trial', 'max_days_banner'); cy.apiSaveCloudTrialBannerPreference(userId, 'trial', 'max_days_banner');
cy.apiSaveActionsMenuPreference(userId);
cy.apiSaveSkipStepsPreference(userId, 'true'); cy.apiSaveSkipStepsPreference(userId, 'true');
cy.apiSaveStartTrialModal(userId, 'true'); cy.apiSaveStartTrialModal(userId, 'true');
cy.apiSaveUnreadScrollPositionPreference(userId, 'start_from_left_off'); cy.apiSaveUnreadScrollPositionPreference(userId, 'start_from_left_off');

View File

@ -81,7 +81,9 @@ describe('channel view actions', () => {
}, },
}, },
general: { general: {
config: {}, config: {
EnableJoinLeaveMessageByDefault: 'true',
},
serverVersion: '5.12.0', serverVersion: '5.12.0',
}, },
roles: { roles: {
@ -589,7 +591,7 @@ describe('channel view actions', () => {
describe('markChannelAsReadOnFocus', () => { describe('markChannelAsReadOnFocus', () => {
test('should mark channel as read when channel is not manually unread', async () => { 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)); await store.dispatch(Actions.markChannelAsReadOnFocus(channel1.id));

View File

@ -43,7 +43,7 @@ const CloudTrialBanner = ({trialEndDate}: Props): JSX.Element | null => {
// the banner when dismissed, will be dismissed for 10 days // the banner when dismissed, will be dismissed for 10 days
const bannerIsStillDismissed = diffDays < 0; const bannerIsStillDismissed = diffDays < 0;
shouldShowBanner = storedDismissedEndDate && bannerIsStillDismissed; shouldShowBanner = Boolean(storedDismissedEndDate) && bannerIsStillDismissed;
} }
const [showBanner, setShowBanner] = useState<boolean>(shouldShowBanner); const [showBanner, setShowBanner] = useState<boolean>(shouldShowBanner);

View File

@ -20,9 +20,9 @@ import type {GlobalState} from 'types/store';
import CloudTrialAnnouncementBar from './cloud_trial_announcement_bar'; import CloudTrialAnnouncementBar from './cloud_trial_announcement_bar';
function mapStateToProps(state: GlobalState) { const getCloudTrialBannerPreferences = makeGetCategory('getCloudTrialBannerPreferences', Preferences.CLOUD_TRIAL_BANNER);
const getCategory = makeGetCategory();
function mapStateToProps(state: GlobalState) {
const subscription = state.entities.cloud.subscription; const subscription = state.entities.cloud.subscription;
const isCloud = getLicense(state).Cloud === 'true'; const isCloud = getLicense(state).Cloud === 'true';
let isFreeTrial = false; let isFreeTrial = false;
@ -43,7 +43,7 @@ function mapStateToProps(state: GlobalState) {
currentUser: getCurrentUser(state), currentUser: getCurrentUser(state),
isCloud, isCloud,
subscription, subscription,
preferences: getCategory(state, Preferences.CLOUD_TRIAL_BANNER), preferences: getCloudTrialBannerPreferences(state),
}; };
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo} from 'react'; import React from 'react';
import {FormattedMessage, defineMessages} from 'react-intl'; import {FormattedMessage, defineMessages} from 'react-intl';
import {useSelector, useDispatch} from 'react-redux'; import {useSelector, useDispatch} from 'react-redux';
@ -30,14 +30,13 @@ import type {GlobalState} from 'types/store';
import AnnouncementBar from '../default_announcement_bar'; import AnnouncementBar from '../default_announcement_bar';
const getCloudTrialEndBannerPreferences = makeGetCategory('getCloudTrialEndBannerPreferences', Preferences.CLOUD_TRIAL_END_BANNER);
const CloudTrialEndAnnouncementBar: React.FC = () => { const CloudTrialEndAnnouncementBar: React.FC = () => {
const limits = useGetLimits(); const limits = useGetLimits();
const subscription = useGetSubscription(); const subscription = useGetSubscription();
const getCategory = useMemo(makeGetCategory, []);
const dispatch = useDispatch(); const dispatch = useDispatch();
const preferences = useSelector((state: GlobalState) => const preferences = useSelector(getCloudTrialEndBannerPreferences);
getCategory(state, Preferences.CLOUD_TRIAL_END_BANNER),
);
const currentUser = useSelector((state: GlobalState) => const currentUser = useSelector((state: GlobalState) =>
getCurrentUser(state), getCurrentUser(state),
); );

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo} from 'react'; import React from 'react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux'; 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 {savePreferences} from 'mattermost-redux/actions/preferences';
import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud';
import {getLicense} from 'mattermost-redux/selectors/entities/general'; 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 {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar'; import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
@ -46,9 +46,8 @@ const OverageUsersBanner = () => {
const license = useSelector(getLicense); const license = useSelector(getLicense);
const seatsPurchased = parseInt(license.Users, 10); const seatsPurchased = parseInt(license.Users, 10);
const isCloud = useSelector(isCurrentLicenseCloud); const isCloud = useSelector(isCurrentLicenseCloud);
const getPreferencesCategory = useMemo(makeGetCategory, []);
const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); 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 activeUsers = ((stats || {})[StatTypes.TOTAL_USERS]) as number || 0;
const { const {
isBetween5PercerntAnd10PercentPurchasedSeats, isBetween5PercerntAnd10PercentPurchasedSeats,

View File

@ -1,14 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {useEffect, useMemo} from 'react'; import {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'; import {useSelector, useDispatch} from 'react-redux';
import type {PreferenceType} from '@mattermost/types/preferences';
import {savePreferences} from 'mattermost-redux/actions/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; 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 {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
@ -29,7 +27,6 @@ import type {GlobalState} from 'types/store';
const ShowStartTrialModal = () => { const ShowStartTrialModal = () => {
const isUserAdmin = useSelector((state: GlobalState) => isCurrentUserSystemAdmin(state)); const isUserAdmin = useSelector((state: GlobalState) => isCurrentUserSystemAdmin(state));
const openStartTrialFormModal = useOpenStartTrialFormModal(); const openStartTrialFormModal = useOpenStartTrialFormModal();
const getCategory = useMemo(makeGetCategory, []);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -40,7 +37,7 @@ const ShowStartTrialModal = () => {
const installationDate = useSelector((state: GlobalState) => getConfig(state).InstallationDate); const installationDate = useSelector((state: GlobalState) => getConfig(state).InstallationDate);
const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); const currentUser = useSelector((state: GlobalState) => getCurrentUser(state));
const preferences = useSelector<GlobalState, PreferenceType[]>((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 prevTrialLicense = useSelector((state: GlobalState) => state.entities.admin.prevTrialLicense);
const currentLicense = useSelector(getLicense); const currentLicense = useSelector(getLicense);
@ -77,7 +74,6 @@ const ShowStartTrialModal = () => {
const now = new Date().getTime(); const now = new Date().getTime();
const hasEnvMoreThan6Hours = now > installationDatePlus6Hours; const hasEnvMoreThan6Hours = now > installationDatePlus6Hours;
const hasEnvMoreThan10Users = Number(totalUsers) > userThreshold; 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) { if (isUserAdmin && !isBenefitsModalOpened && hasEnvMoreThan10Users && hasEnvMoreThan6Hours && !hadAdminDismissedModal && !isLicensedOrPreviousLicensed) {
openStartTrialFormModal({trackingLocation: 'show_start_trial_modal'}, handleOnClose); openStartTrialFormModal({trackingLocation: 'show_start_trial_modal'}, handleOnClose);
trackEvent( trackEvent(

View File

@ -8,18 +8,16 @@ import type {PreferenceType} from '@mattermost/types/preferences';
import {savePreferences} from 'mattermost-redux/actions/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common'; 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 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<ActionResult>] { export default function usePreference(category: string, name: string): [string | undefined, (value: string) => Promise<ActionResult>] {
const dispatch = useDispatch(); const dispatch = useDispatch();
const userId = useSelector(getCurrentUserId); const userId = useSelector(getCurrentUserId);
const preferences = useSelector(getMyPreferences); const preferenceValue = useSelector((state: GlobalState) => getPreference(state, category, name));
const key = getPreferenceKey(category, name);
const preference = preferences[key];
const setPreference = useCallback((value: string) => { const setPreference = useCallback((value: string) => {
const preference: PreferenceType = { const preference: PreferenceType = {
@ -31,5 +29,5 @@ export default function usePreference(category: string, name: string): [string |
return dispatch(savePreferences(userId, [preference])); return dispatch(savePreferences(userId, [preference]));
}, [category, name, userId]); }, [category, name, userId]);
return useMemo(() => ([preference?.value, setPreference]), [preference?.value, setPreference]); return useMemo(() => ([preferenceValue, setPreference]), [preferenceValue, setPreference]);
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo} from 'react'; import React from 'react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux'; 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 {savePreferences} from 'mattermost-redux/actions/preferences';
import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud';
import {getLicense} from 'mattermost-redux/selectors/entities/general'; 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 {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
import AlertBanner from 'components/alert_banner'; import AlertBanner from 'components/alert_banner';
@ -42,9 +42,8 @@ const OverageUsersBannerNotice = () => {
const isGovSku = getIsGovSku(license); const isGovSku = getIsGovSku(license);
const seatsPurchased = parseInt(license.Users, 10); const seatsPurchased = parseInt(license.Users, 10);
const isCloud = useSelector(isCurrentLicenseCloud); const isCloud = useSelector(isCurrentLicenseCloud);
const getPreferencesCategory = useMemo(makeGetCategory, []);
const currentUser = useSelector((state: GlobalState) => getCurrentUser(state)); 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 activeUsers = ((stats || {})[StatTypes.TOTAL_USERS]) as number || 0;
const { const {

View File

@ -9,7 +9,6 @@ import {matchPath, useLocation} from 'react-router-dom';
import {savePreferences} from 'mattermost-redux/actions/preferences'; import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
import {getLicense} from 'mattermost-redux/selectors/entities/general'; 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 {isCurrentUserGuestUser, isCurrentUserSystemAdmin, isFirstAdmin} from 'mattermost-redux/selectors/entities/users';
import {trackEvent as trackEventAction} from 'actions/telemetry_actions'; import {trackEvent as trackEventAction} from 'actions/telemetry_actions';
@ -22,6 +21,7 @@ import {
} from 'actions/views/onboarding_tasks'; } from 'actions/views/onboarding_tasks';
import {setProductMenuSwitcherOpen} from 'actions/views/product_menu'; import {setProductMenuSwitcherOpen} from 'actions/views/product_menu';
import {setStatusDropdown} from 'actions/views/status_dropdown'; import {setStatusDropdown} from 'actions/views/status_dropdown';
import {getOnboardingTaskPreferences} from 'selectors/onboarding';
import Channels from 'components/common/svg_images_components/channels_svg'; import Channels from 'components/common/svg_images_components/channels_svg';
import Gears from 'components/common/svg_images_components/gears_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 {OnboardingTaskCategory, OnboardingTaskList, OnboardingTasksName, TaskNameMapToSteps} from './constants';
import {generateTelemetryTag} from './utils'; import {generateTelemetryTag} from './utils';
const getCategory = makeGetCategory();
const useGetTaskDetails = () => { const useGetTaskDetails = () => {
const {formatMessage} = useIntl(); const {formatMessage} = useIntl();
return { return {
@ -145,7 +143,7 @@ export const useTasksList = () => {
}; };
export const useTasksListWithStatus = () => { export const useTasksListWithStatus = () => {
const dataInDb = useSelector((state: GlobalState) => getCategory(state, OnboardingTaskCategory)); const dataInDb = useSelector(getOnboardingTaskPreferences);
const tasksList = useTasksList(); const tasksList = useTasksList();
const getTaskDetails = useGetTaskDetails(); const getTaskDetails = useGetTaskDetails();
return useMemo(() => return useMemo(() =>

View File

@ -4,16 +4,15 @@
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import type {ConnectedProps} from 'react-redux'; import type {ConnectedProps} from 'react-redux';
import {bindActionCreators} from '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 {Emoji} from '@mattermost/types/emojis';
import type {Post} from '@mattermost/types/posts'; import type {Post} from '@mattermost/types/posts';
import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences';
import {General} from 'mattermost-redux/constants'; import {General} from 'mattermost-redux/constants';
import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels'; import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general'; 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 type {UserActivityPost} from 'mattermost-redux/selectors/entities/posts';
import { import {
get, get,
@ -179,7 +178,7 @@ function makeMapStateToProps() {
channelIsArchived: isArchivedChannel(channel), channelIsArchived: isArchivedChannel(channel),
isConsecutivePost: isConsecutivePost(state, ownProps), isConsecutivePost: isConsecutivePost(state, ownProps),
previousPostIsComment, 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, 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', colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT) === 'true',
shouldShowActionsMenu: shouldShowActionsMenu(state, post), shouldShowActionsMenu: shouldShowActionsMenu(state, post),
@ -220,12 +219,11 @@ function makeMapStateToProps() {
}; };
} }
function mapDispatchToProps(dispatch: Dispatch<AnyAction>) { function mapDispatchToProps(dispatch: Dispatch) {
return { return {
actions: bindActionCreators({ actions: bindActionCreators({
markPostAsUnread, markPostAsUnread,
emitShortcutReactToLastPostFrom, emitShortcutReactToLastPostFrom,
setActionsMenuInitialisationState,
selectPost, selectPost,
selectPostFromRightHandSideSearch, selectPostFromRightHandSideSearch,
setRhsExpanded, setRhsExpanded,

View File

@ -41,7 +41,6 @@ describe('PostComponent', () => {
actions: { actions: {
markPostAsUnread: jest.fn(), markPostAsUnread: jest.fn(),
emitShortcutReactToLastPostFrom: jest.fn(), emitShortcutReactToLastPostFrom: jest.fn(),
setActionsMenuInitialisationState: jest.fn(),
selectPost: jest.fn(), selectPost: jest.fn(),
selectPostFromRightHandSideSearch: jest.fn(), selectPostFromRightHandSideSearch: jest.fn(),
removePost: jest.fn(), removePost: jest.fn(),

View File

@ -91,7 +91,6 @@ export type Props = {
actions: { actions: {
markPostAsUnread: (post: Post, location: string) => void; markPostAsUnread: (post: Post, location: string) => void;
emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void; emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void;
setActionsMenuInitialisationState: (viewed: Record<string, boolean>) => void;
selectPost: (post: Post) => void; selectPost: (post: Post) => void;
selectPostFromRightHandSideSearch: (post: Post) => void; selectPostFromRightHandSideSearch: (post: Post) => void;
removePost: (post: Post) => void; removePost: (post: Post) => void;

View File

@ -8,8 +8,7 @@ import type {Dispatch} from 'redux';
import type {GlobalState} from '@mattermost/types/store'; import type {GlobalState} from '@mattermost/types/store';
import {getMissingProfilesByIds, getMissingProfilesByUsernames} from 'mattermost-redux/actions/users'; import {getMissingProfilesByIds, getMissingProfilesByUsernames} from 'mattermost-redux/actions/users';
import {Preferences} from 'mattermost-redux/constants'; import {shouldShowJoinLeaveMessages} from 'mattermost-redux/selectors/entities/preferences';
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUser, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users';
import CombinedSystemMessage from './combined_system_message'; import CombinedSystemMessage from './combined_system_message';
@ -29,7 +28,7 @@ function makeMapStateToProps() {
return { return {
currentUserId: currentUser.id, currentUserId: currentUser.id,
currentUsername: currentUser.username, currentUsername: currentUser.username,
showJoinLeave: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true), showJoinLeave: shouldShowJoinLeaveMessages(state),
userProfiles: getProfilesByIdsAndUsernames(state, {allUserIds, allUsernames}), userProfiles: getProfilesByIdsAndUsernames(state, {allUserIds, allUsernames}),
}; };
}; };

View File

@ -25,12 +25,10 @@ import {Preferences} from 'utils/constants';
import type {GlobalState} from 'types/store'; import type {GlobalState} from 'types/store';
function makeMapStateToProps() { const getSystemNoticePreferences = makeGetCategory('getSystemNoticePreferences', Preferences.CATEGORY_SYSTEM_NOTICE);
const getCategory = makeGetCategory();
const getPreferenceNameMap = createSelector( const getPreferenceNameMap = createSelector(
'getPreferenceNameMap', 'getPreferenceNameMap',
getCategory, getSystemNoticePreferences,
(preferences) => { (preferences) => {
const nameMap: {[key: string]: PreferenceType} = {}; const nameMap: {[key: string]: PreferenceType} = {};
preferences.forEach((p) => { preferences.forEach((p) => {
@ -40,7 +38,7 @@ function makeMapStateToProps() {
}, },
); );
return function mapStateToProps(state: GlobalState) { function mapStateToProps(state: GlobalState) {
const license = getLicense(state); const license = getLicense(state);
const config = getConfig(state); const config = getConfig(state);
const serverVersion = state.entities.general.serverVersion; const serverVersion = state.entities.general.serverVersion;
@ -48,7 +46,7 @@ function makeMapStateToProps() {
return { return {
currentUserId: state.entities.users.currentUserId, currentUserId: state.entities.users.currentUserId,
preferences: getPreferenceNameMap(state, Preferences.CATEGORY_SYSTEM_NOTICE), preferences: getPreferenceNameMap(state),
dismissedNotices: state.views.notice.hasBeenDismissed, dismissedNotices: state.views.notice.hasBeenDismissed,
isSystemAdmin: haveISystemPermission(state, {permission: Permissions.MANAGE_SYSTEM}), isSystemAdmin: haveISystemPermission(state, {permission: Permissions.MANAGE_SYSTEM}),
notices: Notices, notices: Notices,
@ -58,7 +56,6 @@ function makeMapStateToProps() {
analytics, analytics,
currentChannel: getCurrentChannel(state), currentChannel: getCurrentChannel(state),
}; };
};
} }
function mapDispatchToProps(dispatch: Dispatch) { function mapDispatchToProps(dispatch: Dispatch) {
@ -71,4 +68,4 @@ function mapDispatchToProps(dispatch: Dispatch) {
}; };
} }
export default connect(makeMapStateToProps, mapDispatchToProps)(SystemNotice); export default connect(mapStateToProps, mapDispatchToProps)(SystemNotice);

View File

@ -4,13 +4,12 @@
import React, {memo, useCallback} from 'react'; import React, {memo, useCallback} from 'react';
import type {ReactNode} from 'react'; import type {ReactNode} from 'react';
import {useIntl} from 'react-intl'; 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 type {UserThread} from '@mattermost/types/threads';
import {setThreadFollow, updateThreadRead, markLastPostInThreadAsUnread} from 'mattermost-redux/actions/threads'; import {setThreadFollow, updateThreadRead, markLastPostInThreadAsUnread} from 'mattermost-redux/actions/threads';
import {Preferences} from 'mattermost-redux/constants'; import {isPostFlagged} from 'mattermost-redux/selectors/entities/posts';
import {get} from 'mattermost-redux/selectors/entities/preferences';
import { import {
flagPost as savePost, flagPost as savePost,
@ -56,7 +55,7 @@ function ThreadMenu({
goToInChannel, goToInChannel,
} = useThreadRouting(); } = 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 handleReadUnread = useCallback(() => {
const lastViewedAt = hasUnreads ? Date.now() : unreadTimestamp; const lastViewedAt = hasUnreads ? Date.now() : unreadTimestamp;

View File

@ -24,8 +24,10 @@ import type {GlobalState} from 'types/store';
import AdvancedSettingsDisplay from './user_settings_advanced'; import AdvancedSettingsDisplay from './user_settings_advanced';
import type {OwnProps} from './user_settings_advanced'; import type {OwnProps} from './user_settings_advanced';
function makeMapStateToProps(state: GlobalState, props: OwnProps) { const getAdvancedSettingsCategory = makeGetCategory('getAdvancedSettingsCategory', Preferences.CATEGORY_ADVANCED_SETTINGS);
const getAdvancedSettingsCategory = props.adminMode ? makeGetUserCategory(props.user.id) : makeGetCategory();
function makeMapStateToProps() {
const getUserAdvancedSettingsCategory = makeGetUserCategory('getAdvancedSettingsCategory', Preferences.CATEGORY_ADVANCED_SETTINGS);
return (state: GlobalState, props: OwnProps) => { return (state: GlobalState, props: OwnProps) => {
const config = getConfig(state); const config = getConfig(state);
@ -34,9 +36,10 @@ function makeMapStateToProps(state: GlobalState, props: OwnProps) {
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true'; const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
const userPreferences = props.adminMode && props.userPreferences ? props.userPreferences : undefined; const userPreferences = props.adminMode && props.userPreferences ? props.userPreferences : undefined;
const advancedSettingsCategory = userPreferences ? getUserAdvancedSettingsCategory(state, props.user.id) : getAdvancedSettingsCategory(state);
return { return {
advancedSettingsCategory: getAdvancedSettingsCategory(state, Preferences.CATEGORY_ADVANCED_SETTINGS), advancedSettingsCategory,
sendOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', 'false', userPreferences), 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), codeBlockOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', 'true', userPreferences),
formatting: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true', userPreferences), formatting: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true', userPreferences),

View File

@ -25,7 +25,7 @@ export type OwnProps = {
type Props = OwnProps & { type Props = OwnProps & {
active: boolean; active: boolean;
areAllSectionsInactive: boolean; areAllSectionsInactive: boolean;
joinLeave?: string; joinLeave: string;
onUpdateSection: (section?: string) => void; onUpdateSection: (section?: string) => void;
renderOnOffLabel: (label: string) => ReactNode; renderOnOffLabel: (label: string) => ReactNode;
actions: { actions: {
@ -34,7 +34,7 @@ type Props = OwnProps & {
} }
type State = { type State = {
joinLeaveState?: string; joinLeaveState: string;
isSaving?: boolean; isSaving?: boolean;
serverError?: string; serverError?: string;
} }

View File

@ -108,7 +108,7 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
user_id: userId, user_id: userId,
category: Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, category: Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
name: setting, name: setting,
value: this.state.settings[setting], value: this.state.settings[setting]!,
}); });
}); });

View File

@ -6,28 +6,22 @@ import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux'; import type {Dispatch} from 'redux';
import {saveTheme, deleteTeamSpecificThemes} from 'mattermost-redux/actions/preferences'; import {saveTheme, deleteTeamSpecificThemes} from 'mattermost-redux/actions/preferences';
import {getTheme, makeGetCategory} from 'mattermost-redux/selectors/entities/preferences'; import {getTheme, getThemePreferences} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams';
import {openModal} from 'actions/views/modals'; import {openModal} from 'actions/views/modals';
import {Preferences} from 'utils/constants';
import type {GlobalState} from 'types/store'; import type {GlobalState} from 'types/store';
import UserSettingsTheme from './user_settings_theme'; import UserSettingsTheme from './user_settings_theme';
function makeMapStateToProps() { function mapStateToProps(state: GlobalState) {
const getThemeCategory = makeGetCategory();
return (state: GlobalState) => {
return { return {
currentTeamId: getCurrentTeamId(state), currentTeamId: getCurrentTeamId(state),
theme: getTheme(state), theme: getTheme(state),
applyToAllTeams: getThemeCategory(state, Preferences.CATEGORY_THEME).length <= 1, applyToAllTeams: getThemePreferences(state).length <= 1,
showAllTeamsCheckbox: getMyTeamsCount(state) > 1, showAllTeamsCheckbox: getMyTeamsCount(state) > 1,
}; };
};
} }
function mapDispatchToProps(dispatch: Dispatch) { function mapDispatchToProps(dispatch: Dispatch) {
@ -40,4 +34,4 @@ function mapDispatchToProps(dispatch: Dispatch) {
}; };
} }
export default connect(makeMapStateToProps, mapDispatchToProps)(UserSettingsTheme); export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsTheme);

View File

@ -14,8 +14,8 @@ import {createCustomEmoji} from 'mattermost-redux/actions/emojis';
import * as Actions from 'mattermost-redux/actions/posts'; import * as Actions from 'mattermost-redux/actions/posts';
import {loadMe} from 'mattermost-redux/actions/users'; import {loadMe} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client'; import {Client4} from 'mattermost-redux/client';
import {isPostFlagged} from 'mattermost-redux/selectors/entities/posts';
import type {GetStateFunc} from 'mattermost-redux/types/actions'; import type {GetStateFunc} from 'mattermost-redux/types/actions';
import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';
import mockStore from 'tests/test_store'; import mockStore from 'tests/test_store';
@ -1038,10 +1038,8 @@ describe('Actions.Posts', () => {
reply(200, OK_RESPONSE); reply(200, OK_RESPONSE);
dispatch(Actions.flagPost(post1.id)); dispatch(Actions.flagPost(post1.id));
const state = getState();
const prefKey = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, post1.id); expect(isPostFlagged(getState(), post1.id)).toBe(true);
const preference = state.entities.preferences.myPreferences[prefKey];
expect(preference).toBeTruthy();
}); });
it('unflagPost', async () => { it('unflagPost', async () => {
@ -1069,20 +1067,15 @@ describe('Actions.Posts', () => {
put(`/${TestHelper.basicUser!.id}/preferences`). put(`/${TestHelper.basicUser!.id}/preferences`).
reply(200, OK_RESPONSE); reply(200, OK_RESPONSE);
dispatch(Actions.flagPost(post1.id)); dispatch(Actions.flagPost(post1.id));
let state = getState();
const prefKey = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, post1.id); expect(isPostFlagged(getState(), post1.id)).toBe(true);
const preference = state.entities.preferences.myPreferences[prefKey];
expect(preference).toBeTruthy();
nock(Client4.getUsersRoute()). nock(Client4.getUsersRoute()).
delete(`/${TestHelper.basicUser!.id}/preferences`). delete(`/${TestHelper.basicUser!.id}/preferences`).
reply(200, OK_RESPONSE); reply(200, OK_RESPONSE);
dispatch(Actions.unflagPost(post1.id)); dispatch(Actions.unflagPost(post1.id));
state = getState();
const unflagged = state.entities.preferences.myPreferences[prefKey]; expect(isPostFlagged(getState(), post1.id)).toBe(false);
if (unflagged) {
throw new Error('unexpected unflagged');
}
}); });
it('setUnreadPost', async () => { it('setUnreadPost', async () => {

View File

@ -1234,6 +1234,7 @@ export function unflagPost(postId: string): ActionFuncAsync {
user_id: currentUserId, user_id: currentUserId,
category: Preferences.CATEGORY_FLAGGED_POST, category: Preferences.CATEGORY_FLAGGED_POST,
name: postId, name: postId,
value: 'true',
}; };
Client4.trackEvent('action', 'action_posts_unflag'); Client4.trackEvent('action', 'action_posts_unflag');

View File

@ -5,7 +5,7 @@ import type {PreferenceType} from '@mattermost/types/preferences';
import {PreferenceTypes} from 'mattermost-redux/action_types'; import {PreferenceTypes} from 'mattermost-redux/action_types';
import {Client4} from 'mattermost-redux/client'; 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 type {Theme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import type {ActionFuncAsync, ThunkActionFunc} from 'mattermost-redux/types/actions'; import type {ActionFuncAsync, ThunkActionFunc} from 'mattermost-redux/types/actions';
@ -56,20 +56,6 @@ export function getUserPreferences(userID: string) {
}); });
} }
export function setActionsMenuInitialisationState(initializationState: Record<string, boolean>): ThunkActionFunc<void> {
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<string, boolean>): ThunkActionFunc<void> { export function setCustomStatusInitialisationState(initializationState: Record<string, boolean>): ThunkActionFunc<void> {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
@ -130,7 +116,7 @@ export function deleteTeamSpecificThemes(): ActionFuncAsync {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const themePreferences: PreferenceType[] = makeGetCategory()(state, Preferences.CATEGORY_THEME); const themePreferences: PreferenceType[] = getThemePreferences(state);
const currentUserId = getCurrentUserId(state); const currentUserId = getCurrentUserId(state);
const toDelete = themePreferences.filter((pref) => pref.name !== ''); const toDelete = themePreferences.filter((pref) => pref.name !== '');

View File

@ -37,10 +37,6 @@ const Preferences = {
MENTION_KEYS: 'mention_keys', MENTION_KEYS: 'mention_keys',
USE_MILITARY_TIME: 'use_military_time', 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', CATEGORY_CUSTOM_STATUS: 'custom_status',
NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state', NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state',
NAME_RECENT_CUSTOM_STATUSES: 'recent_custom_statuses', NAME_RECENT_CUSTOM_STATUSES: 'recent_custom_statuses',
@ -82,6 +78,8 @@ const Preferences = {
HIDE_BATCH_EXPORT_CONFIRM_MODAL: 'hide_batch_export_confirm_modal', HIDE_BATCH_EXPORT_CONFIRM_MODAL: 'hide_batch_export_confirm_modal',
HIDE_MYSQL_STATS_NOTIFICATION: 'hide_mysql_stats_notifcation', HIDE_MYSQL_STATS_NOTIFICATION: 'hide_mysql_stats_notifcation',
CATEGORY_OVERAGE_USERS_BANNER: 'overage_users_banner',
CATEGORY_THEME: 'theme', CATEGORY_THEME: 'theme',
THEMES: { THEMES: {
denim: { denim: {

View File

@ -253,6 +253,11 @@ describe('Selectors.Posts', () => {
const testStateAny = deepFreezeAndThrowOnMutation({ const testStateAny = deepFreezeAndThrowOnMutation({
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
users: { users: {
currentUserId: userAny.id, currentUserId: userAny.id,
profiles: profilesAny, profiles: profilesAny,
@ -372,6 +377,11 @@ describe('Selectors.Posts', () => {
const testStateRoot = deepFreezeAndThrowOnMutation({ const testStateRoot = deepFreezeAndThrowOnMutation({
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
users: { users: {
currentUserId: userRoot.id, currentUserId: userRoot.id,
profiles: profilesRoot, profiles: profilesRoot,
@ -485,6 +495,11 @@ describe('Selectors.Posts', () => {
const testStateNever = deepFreezeAndThrowOnMutation({ const testStateNever = deepFreezeAndThrowOnMutation({
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
users: { users: {
currentUserId: userNever.id, currentUserId: userNever.id,
profiles: profilesNever, profiles: profilesNever,
@ -601,6 +616,11 @@ describe('Selectors.Posts', () => {
const testStateAny = deepFreezeAndThrowOnMutation({ const testStateAny = deepFreezeAndThrowOnMutation({
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
users: { users: {
currentUserId: userAny.id, currentUserId: userAny.id,
profiles: profilesAny, profiles: profilesAny,
@ -678,6 +698,11 @@ describe('Selectors.Posts', () => {
const testStateAny = deepFreezeAndThrowOnMutation({ const testStateAny = deepFreezeAndThrowOnMutation({
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
users: { users: {
currentUserId: userAny.id, currentUserId: userAny.id,
profiles: profilesAny, profiles: profilesAny,
@ -1823,6 +1848,11 @@ describe('Selectors.Posts', () => {
}; };
const state = { const state = {
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: testPosts, posts: testPosts,
postsInChannel: { postsInChannel: {
@ -1852,6 +1882,11 @@ describe('Selectors.Posts', () => {
}; };
const state = { const state = {
entities: { entities: {
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: testPosts, posts: testPosts,
postsInChannel: { postsInChannel: {
@ -1880,6 +1915,11 @@ describe('Selectors.Posts', () => {
channels: { channels: {
currentChannelId: 'abcd', currentChannelId: 'abcd',
}, },
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: {}, posts: {},
postsInChannel: [], postsInChannel: [],
@ -1910,6 +1950,11 @@ describe('Selectors.Posts', () => {
channels: { channels: {
currentChannelId: 'abcd', currentChannelId: 'abcd',
}, },
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: postsAny, posts: postsAny,
postsInChannel: { postsInChannel: {
@ -2147,6 +2192,11 @@ describe('getPostsInCurrentChannel', () => {
channels: { channels: {
currentChannelId: 'channel1', currentChannelId: 'channel1',
}, },
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: {}, posts: {},
postsInChannel: {}, postsInChannel: {},
@ -2177,6 +2227,11 @@ describe('getPostsInCurrentChannel', () => {
channels: { channels: {
currentChannelId: 'channel1', currentChannelId: 'channel1',
}, },
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: { posts: {
post1, post1,
@ -2214,6 +2269,11 @@ describe('getPostsInCurrentChannel', () => {
channels: { channels: {
currentChannelId: 'channel1', currentChannelId: 'channel1',
}, },
general: {
config: {
EnableJoinLeaveMessageByDefault: 'true',
},
},
posts: { posts: {
posts: { posts: {
post1, post1,

View File

@ -24,11 +24,10 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentChannelId, getCurrentUser} from 'mattermost-redux/selectors/entities/common'; import {getCurrentChannelId, getCurrentUser} from 'mattermost-redux/selectors/entities/common';
import {getConfig} from 'mattermost-redux/selectors/entities/general'; 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 {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getUsers, getCurrentUserId, getUserStatuses} from 'mattermost-redux/selectors/entities/users'; import {getUsers, getCurrentUserId, getUserStatuses} from 'mattermost-redux/selectors/entities/users';
import {createIdsSelector} from 'mattermost-redux/utils/helpers'; import {createIdsSelector} from 'mattermost-redux/utils/helpers';
import {shouldShowJoinLeaveMessages} from 'mattermost-redux/utils/post_list';
import { import {
isPostEphemeral, isPostEphemeral,
isSystemMessage, isSystemMessage,
@ -37,7 +36,6 @@ import {
isPostPendingOrFailed, isPostPendingOrFailed,
isPostCommentMention, isPostCommentMention,
} from 'mattermost-redux/utils/post_utils'; } from 'mattermost-redux/utils/post_utils';
import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';
import {isGuest} from 'mattermost-redux/utils/user_utils'; import {isGuest} from 'mattermost-redux/utils/user_utils';
export function getAllPosts(state: GlobalState) { export function getAllPosts(state: GlobalState) {
@ -53,6 +51,10 @@ export function getPost(state: GlobalState, postId: Post['id']): Post {
return getAllPosts(state)[postId]; 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 { export function getPostRepliesCount(state: GlobalState, postId: Post['id']): number {
return state.entities.posts.postsReplies[postId] || 0; return state.entities.posts.postsReplies[postId] || 0;
} }
@ -344,18 +346,15 @@ export function makeGetPostsInChannel(): (state: GlobalState, channelId: Channel
getPostsInThread, getPostsInThread,
(state: GlobalState, channelId: Channel['id']) => getPostIdsInChannel(state, channelId), (state: GlobalState, channelId: Channel['id']) => getPostIdsInChannel(state, channelId),
getCurrentUser, getCurrentUser,
getMyPreferences, shouldShowJoinLeaveMessages,
(state: GlobalState, channelId: Channel['id'], numPosts: number) => numPosts || Posts.POST_CHUNK_SIZE, (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) { if (!allPostIds) {
return null; return null;
} }
const posts: PostWithFormatData[] = []; 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); const postIds = numPosts === -1 ? allPostIds : allPostIds.slice(0, numPosts);
for (let i = 0; i < postIds.length; i++) { for (let i = 0; i < postIds.length; i++) {
@ -388,15 +387,13 @@ export function makeGetPostsAroundPost(): (state: GlobalState, postId: Post['id'
getPostsInThread, getPostsInThread,
(state: GlobalState, focusedPostId) => focusedPostId, (state: GlobalState, focusedPostId) => focusedPostId,
getCurrentUser, getCurrentUser,
getMyPreferences, shouldShowJoinLeaveMessages,
(postIds, allPosts, postsInThread, focusedPostId, currentUser, myPreferences) => { (postIds, allPosts, postsInThread, focusedPostId, currentUser, showJoinLeave) => {
if (!postIds || !currentUser) { if (!postIds || !currentUser) {
return null; return null;
} }
const posts: PostWithFormatData[] = []; 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++) { for (let i = 0; i < postIds.length; i++) {
const post = allPosts[postIds[i]]; const post = allPosts[postIds[i]];
@ -565,13 +562,11 @@ export const getMostRecentPostIdInChannel: (state: GlobalState, channelId: Chann
'getMostRecentPostIdInChannel', 'getMostRecentPostIdInChannel',
getAllPosts, getAllPosts,
(state: GlobalState, channelId: string) => getPostIdsInChannel(state, channelId), (state: GlobalState, channelId: string) => getPostIdsInChannel(state, channelId),
getMyPreferences, shouldShowJoinLeaveMessages,
(posts, postIdsInChannel, preferences) => { (posts, postIdsInChannel, allowSystemMessages) => {
if (!postIdsInChannel) { if (!postIdsInChannel) {
return ''; return '';
} }
const key = getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE);
const allowSystemMessages = preferences[key] ? preferences[key].value === 'true' : true;
if (!allowSystemMessages) { if (!allowSystemMessages) {
// return the most recent non-system message in the channel // return the most recent non-system message in the channel

View File

@ -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', () => { it('get direct channel show preferences', () => {
expect(Selectors.getDirectShowPreferences(testState)).toEqual([dmPref1, dmPref2]); 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', () => { describe('shouldShowUnreadsCategory', () => {
test('should return value from the preference if set', () => { test('should return value from the preference if set', () => {
const state = { const state = {

View File

@ -8,7 +8,7 @@ import type {GlobalState} from '@mattermost/types/store';
import {General, Preferences} from 'mattermost-redux/constants'; import {General, Preferences} from 'mattermost-redux/constants';
import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {getConfig, getFeatureFlagValue, getLicense} from 'mattermost-redux/selectors/entities/general'; 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 {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';
import {setThemeDefaults} from 'mattermost-redux/utils/theme_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]; 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) { if (preferences) {
return getFromPreferences(preferences, category, name, defaultValue); return getFromPreferences(preferences, category, name, defaultValue);
} }
const key = getPreferenceKey(category, name); const pref = getPreferenceObject(state, category, name);
const prefs = getMyPreferences(state);
if (!(key in prefs)) { return pref ? pref.value : defaultValue;
return defaultValue;
}
return prefs[key].value;
} }
export function getFromPreferences(preferences: PreferencesType, category: string, name: string, defaultValue: any = '') { 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 { 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); return parseInt(value, 10);
} }
export function makeGetCategory(): (state: GlobalState, category: string) => PreferenceType[] { export function makeGetCategory(selectorName: string, category: string): (state: GlobalState) => PreferenceType[] {
return createSelector( return createIdsSelector(
'makeGetCategory', selectorName,
getMyPreferences, getMyPreferences,
(state: GlobalState, category: string) => category, (preferences) => {
(preferences, category) => {
const prefix = category + '--'; const prefix = category + '--';
const prefsInCategory: PreferenceType[] = []; 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[] { export function makeGetUserCategory(selectorName: string, category: string): (state: GlobalState, userID: string) => PreferenceType[] {
return createSelector( return createIdsSelector(
'makeGetCategory', selectorName,
(state) => getUserPreferences(state, userID), (state: GlobalState, userID: string) => getUserPreferences(state, userID),
(state: GlobalState, category: string) => category, (preferences) => {
(preferences, category) => {
const prefix = category + '--'; const prefix = category + '--';
const prefsInCategory: PreferenceType[] = []; const prefsInCategory: PreferenceType[] = [];
@ -95,28 +92,18 @@ export function makeGetUserCategory(userID: string): (state: GlobalState, catego
); );
} }
const getDirectShowCategory = makeGetCategory(); export const getDirectShowPreferences = makeGetCategory('getDirectShowPreferences', Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
export const getGroupShowPreferences = makeGetCategory('getGroupShowPreferences', Preferences.CATEGORY_GROUP_CHANNEL_SHOW);
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 getTeammateNameDisplaySetting: (state: GlobalState) => string = createSelector( export const getTeammateNameDisplaySetting: (state: GlobalState) => string = createSelector(
'getTeammateNameDisplaySetting', 'getTeammateNameDisplaySetting',
getConfig, getConfig,
getMyPreferences, (state) => getPreferenceObject(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT),
getLicense, getLicense,
(config, preferences, license) => { (config, teammateNameDisplayPreference, license) => {
const useAdminTeammateNameDisplaySetting = (license && license.LockTeammateNameDisplay === 'true') && config.LockTeammateNameDisplay === 'true'; const useAdminTeammateNameDisplaySetting = (license && license.LockTeammateNameDisplay === 'true') && config.LockTeammateNameDisplay === 'true';
const key = getPreferenceKey(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT); if (teammateNameDisplayPreference && !useAdminTeammateNameDisplaySetting) {
if (preferences[key] && !useAdminTeammateNameDisplaySetting) { return teammateNameDisplayPreference.value || '';
return preferences[key].value || '';
} else if (config.TeammateNameDisplay) { } else if (config.TeammateNameDisplay) {
return 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( const getThemePreference = createSelector(
'getThemePreference', 'getThemePreference',
getMyPreferences, getMyPreferences,
@ -218,6 +207,14 @@ export function makeGetStyleFromTheme<Style>(): (state: GlobalState, getStyleFro
); );
} }
export function shouldShowJoinLeaveMessages(state: GlobalState) {
const config = getConfig(state);
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
// This setting is true or not set if join/leave messages are to be displayed
return getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, enableJoinLeaveMessage);
}
// shouldShowUnreadsCategory returns true if the user has unereads grouped separately with the new sidebar enabled. // shouldShowUnreadsCategory returns true if the user has unereads grouped separately with the new sidebar enabled.
export const shouldShowUnreadsCategory: (state: GlobalState, userPreferences?: PreferencesType) => boolean = createSelector( export const shouldShowUnreadsCategory: (state: GlobalState, userPreferences?: PreferencesType) => boolean = createSelector(
'shouldShowUnreadsCategory', 'shouldShowUnreadsCategory',
@ -336,3 +333,5 @@ export function moveThreadsEnabled(state: GlobalState): boolean {
export function streamlinedMarketplaceEnabled(state: GlobalState): boolean { export function streamlinedMarketplaceEnabled(state: GlobalState): boolean {
return getFeatureFlagValue(state, 'StreamlinedMarketplace') === 'true'; return getFeatureFlagValue(state, 'StreamlinedMarketplace') === 'true';
} }
export const getOverageBannerPreferences = makeGetCategory('getOverageBannerPreferences', Preferences.CATEGORY_OVERAGE_USERS_BANNER);

View File

@ -23,7 +23,6 @@ import {
makeGenerateCombinedPost, makeGenerateCombinedPost,
extractUserActivityData, extractUserActivityData,
START_OF_NEW_MESSAGES, START_OF_NEW_MESSAGES,
shouldShowJoinLeaveMessages,
} from './post_list'; } from './post_list';
import TestHelper from '../../test/test_helper'; import TestHelper from '../../test/test_helper';
@ -1688,95 +1687,3 @@ describe('combineUserActivityData', () => {
expect(combineUserActivitySystemPost(posts)).toEqual(expectedOutput); expect(combineUserActivitySystemPost(posts)).toEqual(expectedOutput);
}); });
}); });
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 = 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 = 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 = 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 = shouldShowJoinLeaveMessages(state);
expect(show).toEqual(false);
});
});

View File

@ -6,12 +6,11 @@ import moment from 'moment-timezone';
import type {ActivityEntry, Post} from '@mattermost/types/posts'; import type {ActivityEntry, Post} from '@mattermost/types/posts';
import type {GlobalState} from '@mattermost/types/store'; import type {GlobalState} from '@mattermost/types/store';
import {Posts, Preferences} from 'mattermost-redux/constants'; import {Posts} from 'mattermost-redux/constants';
import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {makeGetPostsForIds} from 'mattermost-redux/selectors/entities/posts'; import {makeGetPostsForIds} from 'mattermost-redux/selectors/entities/posts';
import type {UserActivityPost} from 'mattermost-redux/selectors/entities/posts'; import type {UserActivityPost} from 'mattermost-redux/selectors/entities/posts';
import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {shouldShowJoinLeaveMessages} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {createIdsSelector, memoizeResult} from 'mattermost-redux/utils/helpers'; import {createIdsSelector, memoizeResult} from 'mattermost-redux/utils/helpers';
import {isUserActivityPost, shouldFilterJoinLeavePost, isFromWebhook} from 'mattermost-redux/utils/post_utils'; import {isUserActivityPost, shouldFilterJoinLeavePost, isFromWebhook} from 'mattermost-redux/utils/post_utils';
@ -23,14 +22,6 @@ export const DATE_LINE = 'date-';
export const START_OF_NEW_MESSAGES = 'start-of-new-messages-'; export const START_OF_NEW_MESSAGES = 'start-of-new-messages-';
export const MAX_COMBINED_SYSTEM_POSTS = 100; export const MAX_COMBINED_SYSTEM_POSTS = 100;
export function shouldShowJoinLeaveMessages(state: GlobalState) {
const config = getConfig(state);
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
// This setting is true or not set if join/leave messages are to be displayed
return getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, enableJoinLeaveMessage);
}
interface PostFilterOptions { interface PostFilterOptions {
postIds: string[]; postIds: string[];
lastViewedAt: number; lastViewedAt: number;

View File

@ -3,24 +3,14 @@
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
import type {Post, PostType, PostMetadata, PostEmbed} from '@mattermost/types/posts'; import type {Post, PostType, PostMetadata, PostEmbed} from '@mattermost/types/posts';
import type {PreferenceType} from '@mattermost/types/preferences';
import type {GlobalState} from '@mattermost/types/store'; import type {GlobalState} from '@mattermost/types/store';
import type {Team} from '@mattermost/types/teams'; import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
import {getPreferenceKey} from './preference_utils';
import {Posts, Preferences, Permissions} from '../constants'; import {Posts, Preferences, Permissions} from '../constants';
export function isPostFlagged(postId: Post['id'], myPreferences: {
[x: string]: PreferenceType;
}): boolean {
const key = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, postId);
return myPreferences.hasOwnProperty(key);
}
export function isSystemMessage(post: Post): boolean { export function isSystemMessage(post: Post): boolean {
return Boolean(post.type && post.type.startsWith(Posts.SYSTEM_MESSAGE_PREFIX)); return Boolean(post.type && post.type.startsWith(Posts.SYSTEM_MESSAGE_PREFIX));
} }

View File

@ -1,18 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Preferences} from 'mattermost-redux/constants';
import {get} from 'mattermost-redux/selectors/entities/preferences';
import {getIsMobileView} from 'selectors/views/browser';
import type {GlobalState} from 'types/store';
export function showActionsDropdownPulsatingDot(state: GlobalState): boolean {
if (getIsMobileView(state)) {
return false;
}
const actionsMenuTutorialState = get(state, Preferences.CATEGORY_ACTIONS_MENU, Preferences.NAME_ACTIONS_MENU_TUTORIAL_STATE, false);
const modalAlreadyViewed = actionsMenuTutorialState && JSON.parse(actionsMenuTutorialState)[Preferences.ACTIONS_MENU_VIEWED];
return !modalAlreadyViewed;
}

View File

@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {makeGetCategory, getBool} from 'mattermost-redux/selectors/entities/preferences'; import {get as getString, getBool, makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser, isFirstAdmin} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUser, isFirstAdmin} from 'mattermost-redux/selectors/entities/users';
import {getIsMobileView} from 'selectors/views/browser'; import {getIsMobileView} from 'selectors/views/browser';
@ -13,21 +13,8 @@ import {RecommendedNextStepsLegacy, Preferences} from 'utils/constants';
import type {GlobalState} from 'types/store'; import type {GlobalState} from 'types/store';
const getABTestCategory = makeGetCategory();
export const getABTestPreferences = (() => {
return (state: GlobalState) => getABTestCategory(state, Preferences.AB_TEST_PREFERENCE_VALUE);
})();
const getFirstChannelNamePref = createSelector(
'getFirstChannelNamePref',
getABTestPreferences,
(preferences) => {
return preferences.find((pref) => pref.name === RecommendedNextStepsLegacy.CREATE_FIRST_CHANNEL);
},
);
export function getFirstChannelName(state: GlobalState) { export function getFirstChannelName(state: GlobalState) {
return getFirstChannelNamePref(state)?.value || ''; return getString(state, Preferences.AB_TEST_PREFERENCE_VALUE, RecommendedNextStepsLegacy.CREATE_FIRST_CHANNEL, '');
} }
export function getShowLaunchingWorkspace(state: GlobalState) { export function getShowLaunchingWorkspace(state: GlobalState) {
@ -91,13 +78,13 @@ const getSteps = createSelector(
}, },
); );
const getNextStepsCategory = makeGetCategory(); const getNextStepsPreferences = makeGetCategory('getNextStepsPreferences', Preferences.RECOMMENDED_NEXT_STEPS);
const getOnboardingTaskCategory = makeGetCategory(); export const getOnboardingTaskPreferences = makeGetCategory('getOnboardingTaskPreferences', OnboardingTaskCategory);
// Loop through all Steps. For each step, check that // Loop through all Steps. For each step, check that
export const legacyNextStepsNotFinished = createSelector( export const legacyNextStepsNotFinished = createSelector(
'legacyNextStepsNotFinished', 'legacyNextStepsNotFinished',
(state: GlobalState) => getNextStepsCategory(state, Preferences.RECOMMENDED_NEXT_STEPS), getNextStepsPreferences,
(state: GlobalState) => getCurrentUser(state), (state: GlobalState) => getCurrentUser(state),
(state: GlobalState) => isFirstAdmin(state), (state: GlobalState) => isFirstAdmin(state),
(state: GlobalState) => getSteps(state), (state: GlobalState) => getSteps(state),
@ -111,7 +98,7 @@ export const legacyNextStepsNotFinished = createSelector(
// Loop through all Steps. For each step, check that // Loop through all Steps. For each step, check that
export const hasLegacyNextStepsPreferences = createSelector( export const hasLegacyNextStepsPreferences = createSelector(
'hasLegacyNextStepsPreferences', 'hasLegacyNextStepsPreferences',
(state: GlobalState) => getNextStepsCategory(state, Preferences.RECOMMENDED_NEXT_STEPS), getNextStepsPreferences,
(state: GlobalState) => getSteps(state), (state: GlobalState) => getSteps(state),
(stepPreferences, mySteps) => { (stepPreferences, mySteps) => {
const checkPref = (step: StepType) => stepPreferences.some((pref) => (pref.name === step.id)); const checkPref = (step: StepType) => stepPreferences.some((pref) => (pref.name === step.id));
@ -121,8 +108,8 @@ export const hasLegacyNextStepsPreferences = createSelector(
export const getShowTaskListBool = createSelector( export const getShowTaskListBool = createSelector(
'getShowTaskListBool', 'getShowTaskListBool',
(state: GlobalState) => getOnboardingTaskCategory(state, OnboardingTaskCategory), getOnboardingTaskPreferences,
(state: GlobalState) => getNextStepsCategory(state, Preferences.RECOMMENDED_NEXT_STEPS), getNextStepsPreferences,
getIsMobileView, getIsMobileView,
(state: GlobalState) => getBool(state, OnboardingTaskCategory, OnboardingTaskList.ONBOARDING_TASK_LIST_SHOW), (state: GlobalState) => getBool(state, OnboardingTaskCategory, OnboardingTaskList.ONBOARDING_TASK_LIST_SHOW),
(state: GlobalState) => hasLegacyNextStepsPreferences(state), (state: GlobalState) => hasLegacyNextStepsPreferences(state),

View File

@ -103,7 +103,6 @@ export const Preferences = {
UNREAD_SCROLL_POSITION_START_FROM_LEFT: 'start_from_left_off', UNREAD_SCROLL_POSITION_START_FROM_LEFT: 'start_from_left_off',
UNREAD_SCROLL_POSITION_START_FROM_NEWEST: 'start_from_newest', UNREAD_SCROLL_POSITION_START_FROM_NEWEST: 'start_from_newest',
CATEGORY_THEME: 'theme', CATEGORY_THEME: 'theme',
CATEGORY_FLAGGED_POST: 'flagged_post',
CATEGORY_NOTIFICATIONS: 'notifications', CATEGORY_NOTIFICATIONS: 'notifications',
EMAIL_INTERVAL: 'email_interval', EMAIL_INTERVAL: 'email_interval',
INTERVAL_IMMEDIATE: 30, // "immediate" is a 30 second interval INTERVAL_IMMEDIATE: 30, // "immediate" is a 30 second interval
@ -147,7 +146,7 @@ export const Preferences = {
DELINQUENCY_MODAL_CONFIRMED: 'delinquency_modal_confirmed', DELINQUENCY_MODAL_CONFIRMED: 'delinquency_modal_confirmed',
CONFIGURATION_BANNERS: 'configuration_banners', CONFIGURATION_BANNERS: 'configuration_banners',
NOTIFY_ADMIN_REVOKE_DOWNGRADED_WORKSPACE: 'admin_revoke_downgraded_instance', NOTIFY_ADMIN_REVOKE_DOWNGRADED_WORKSPACE: 'admin_revoke_downgraded_instance',
OVERAGE_USERS_BANNER: 'overage_users_banner', OVERAGE_USERS_BANNER: ReduxPreferences.CATEGORY_OVERAGE_USERS_BANNER,
TO_CLOUD_YEARLY_PLAN_NUDGE: 'to_cloud_yearly_plan_nudge', TO_CLOUD_YEARLY_PLAN_NUDGE: 'to_cloud_yearly_plan_nudge',
TO_PAID_PLAN_NUDGE: 'to_paid_plan_nudge', TO_PAID_PLAN_NUDGE: 'to_paid_plan_nudge',
CLOUD_ANNUAL_RENEWAL_BANNER: 'cloud_annual_renewal_banner', CLOUD_ANNUAL_RENEWAL_BANNER: 'cloud_annual_renewal_banner',

View File

@ -21,8 +21,8 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getAllGroupsForReferenceByName} from 'mattermost-redux/selectors/entities/groups'; import {getAllGroupsForReferenceByName} from 'mattermost-redux/selectors/entities/groups';
import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts'; import {isPostFlagged, makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
import {get, getTeammateNameDisplaySetting, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; import {getTeammateNameDisplaySetting, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
import {getCurrentTeamId, getTeam} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeamId, getTeam} from 'mattermost-redux/selectors/entities/teams';
import {makeGetDisplayName, getCurrentUserId, getUser, getUsersByUsername} from 'mattermost-redux/selectors/entities/users'; import {makeGetDisplayName, getCurrentUserId, getUser, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
@ -36,7 +36,7 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {getEmojiMap} from 'selectors/emojis'; import {getEmojiMap} from 'selectors/emojis';
import {getIsMobileView} from 'selectors/views/browser'; import {getIsMobileView} from 'selectors/views/browser';
import Constants, {PostListRowListIds, Preferences} from 'utils/constants'; import Constants, {PostListRowListIds} from 'utils/constants';
import * as Keyboard from 'utils/keyboard'; import * as Keyboard from 'utils/keyboard';
import {formatWithRenderer} from 'utils/markdown'; import {formatWithRenderer} from 'utils/markdown';
import MentionableRenderer from 'utils/markdown/mentionable_renderer'; import MentionableRenderer from 'utils/markdown/mentionable_renderer';
@ -471,8 +471,8 @@ export function usePostAriaLabel(post: Post | undefined) {
} }
const authorDisplayName = getDisplayName(state, post.user_id); const authorDisplayName = getDisplayName(state, post.user_id);
const reactions = getReactionsForPost(state, post?.id); const reactions = getReactionsForPost(state, post.id);
const isFlagged = get(state, Preferences.CATEGORY_FLAGGED_POST, post.id, null) != null; const isFlagged = isPostFlagged(state, post.id);
const emojiMap = getEmojiMap(state); const emojiMap = getEmojiMap(state);
const mentions = getMentionsFromMessage(state, post); const mentions = getMentionsFromMessage(state, post);
const teammateNameDisplaySetting = getTeammateNameDisplaySetting(state); const teammateNameDisplaySetting = getTeammateNameDisplaySetting(state);

View File

@ -5,7 +5,7 @@ export type PreferenceType = {
category: string; category: string;
name: string; name: string;
user_id: string; user_id: string;
value?: string; value: string;
}; };
export type PreferencesType = { export type PreferencesType = {