diff --git a/server/config/client.go b/server/config/client.go index 9faa50fc18..ff7e599fee 100644 --- a/server/config/client.go +++ b/server/config/client.go @@ -23,6 +23,7 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["LockTeammateNameDisplay"] = strconv.FormatBool(*c.TeamSettings.LockTeammateNameDisplay) props["ExperimentalPrimaryTeam"] = *c.TeamSettings.ExperimentalPrimaryTeam props["ExperimentalViewArchivedChannels"] = strconv.FormatBool(*c.TeamSettings.ExperimentalViewArchivedChannels) + props["EnableJoinLeaveMessageByDefault"] = strconv.FormatBool(*c.TeamSettings.EnableJoinLeaveMessageByDefault) props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation) props["EnableOAuthServiceProvider"] = strconv.FormatBool(*c.ServiceSettings.EnableOAuthServiceProvider) @@ -241,6 +242,7 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m props["WebsocketSecurePort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketSecurePort) props["EnableUserCreation"] = strconv.FormatBool(*c.TeamSettings.EnableUserCreation) props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer) + props["EnableJoinLeaveMessageByDefault"] = strconv.FormatBool(*c.TeamSettings.EnableJoinLeaveMessageByDefault) props["AndroidLatestVersion"] = c.ClientRequirements.AndroidLatestVersion props["AndroidMinVersion"] = c.ClientRequirements.AndroidMinVersion diff --git a/server/config/client_test.go b/server/config/client_test.go index 7c1f7212bf..89cb88e02b 100644 --- a/server/config/client_test.go +++ b/server/config/client_test.go @@ -271,6 +271,28 @@ func TestGetClientConfig(t *testing.T) { "DisableAppBar": "true", }, }, + { + "default EnableJoinLeaveMessage", + &model.Config{}, + "tag1", + nil, + map[string]string{ + "EnableJoinLeaveMessageByDefault": "true", + }, + }, + { + "disable EnableJoinLeaveMessage", + &model.Config{ + TeamSettings: model.TeamSettings{ + EnableJoinLeaveMessageByDefault: model.NewBool(false), + }, + }, + "tag1", + nil, + map[string]string{ + "EnableJoinLeaveMessageByDefault": "false", + }, + }, } for _, testCase := range testCases { diff --git a/server/public/model/config.go b/server/public/model/config.go index 960e9ba681..19ac0889b9 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -2129,18 +2129,19 @@ func (s *ThemeSettings) SetDefaults() { } type TeamSettings struct { - SiteName *string `access:"site_customization"` - MaxUsersPerTeam *int `access:"site_users_and_teams"` - EnableUserCreation *bool `access:"authentication_signup"` - EnableOpenServer *bool `access:"authentication_signup"` - EnableUserDeactivation *bool `access:"experimental_features"` - RestrictCreationToDomains *string `access:"authentication_signup"` // telemetry: none - EnableCustomUserStatuses *bool `access:"site_users_and_teams"` - EnableCustomBrand *bool `access:"site_customization"` - CustomBrandText *string `access:"site_customization"` - CustomDescriptionText *string `access:"site_customization"` - RestrictDirectMessage *string `access:"site_users_and_teams"` - EnableLastActiveTime *bool `access:"site_users_and_teams"` + SiteName *string `access:"site_customization"` + MaxUsersPerTeam *int `access:"site_users_and_teams"` + EnableJoinLeaveMessageByDefault *bool `access:"site_users_and_teams"` + EnableUserCreation *bool `access:"authentication_signup"` + EnableOpenServer *bool `access:"authentication_signup"` + EnableUserDeactivation *bool `access:"experimental_features"` + RestrictCreationToDomains *string `access:"authentication_signup"` // telemetry: none + EnableCustomUserStatuses *bool `access:"site_users_and_teams"` + EnableCustomBrand *bool `access:"site_customization"` + CustomBrandText *string `access:"site_customization"` + CustomDescriptionText *string `access:"site_customization"` + RestrictDirectMessage *string `access:"site_users_and_teams"` + EnableLastActiveTime *bool `access:"site_users_and_teams"` // In seconds. UserStatusAwayTimeout *int64 `access:"experimental_features"` MaxChannelsPerTeam *int64 `access:"site_users_and_teams"` @@ -2164,6 +2165,10 @@ func (s *TeamSettings) SetDefaults() { s.MaxUsersPerTeam = NewInt(TeamSettingsDefaultMaxUsersPerTeam) } + if s.EnableJoinLeaveMessageByDefault == nil { + s.EnableJoinLeaveMessageByDefault = NewBool(true) + } + if s.EnableUserCreation == nil { s.EnableUserCreation = NewBool(true) } diff --git a/server/public/model/config_test.go b/server/public/model/config_test.go index 97a73686db..20497d1349 100644 --- a/server/public/model/config_test.go +++ b/server/public/model/config_test.go @@ -335,6 +335,14 @@ func TestTeamSettingsIsValidSiteNameEmpty(t *testing.T) { require.Nil(t, c1.TeamSettings.isValid()) } +func TestTeamSettingsDefaultJoinLeaveMessage(t *testing.T) { + c1 := Config{} + c1.SetDefaults() + + // should default to true + require.Equal(t, NewBool(true), c1.TeamSettings.EnableJoinLeaveMessageByDefault) +} + func TestMessageExportSettingsIsValidEnableExportNotSet(t *testing.T) { mes := &MessageExportSettings{} diff --git a/webapp/channels/src/components/admin_console/admin_definition.jsx b/webapp/channels/src/components/admin_console/admin_definition.jsx index 203ce21f5a..def880e2d6 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.jsx +++ b/webapp/channels/src/components/admin_console/admin_definition.jsx @@ -2277,6 +2277,15 @@ const AdminDefinition = { placeholder_default: 'E.g.: "100"', isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.USERS_AND_TEAMS)), }, + { + type: Constants.SettingsTypes.TYPE_BOOL, + key: 'TeamSettings.EnableJoinLeaveMessageByDefault', + label: t('admin.team.enableJoinLeaveMessageTitle'), + label_default: 'Enable join/leave messages by default:', + help_text: t('admin.team.enableJoinLeaveMessageDescription'), + help_text_default: 'Choose the default configuration of system messages displayed when users join or leave channels. Users can override this default by configuring Join/Leave messages in Account Settings > Advanced.', + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.USERS_AND_TEAMS)), + }, { type: Constants.SettingsTypes.TYPE_DROPDOWN, key: 'TeamSettings.RestrictDirectMessage', diff --git a/webapp/channels/src/components/user_settings/advanced/index.ts b/webapp/channels/src/components/user_settings/advanced/index.ts index 833f39e6b2..05d9b42ba9 100644 --- a/webapp/channels/src/components/user_settings/advanced/index.ts +++ b/webapp/channels/src/components/user_settings/advanced/index.ts @@ -25,13 +25,14 @@ function makeMapStateToProps() { const enablePreviewFeatures = config.EnablePreviewFeatures === 'true'; const enableUserDeactivation = config.EnableUserDeactivation === 'true'; + const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true'; return { advancedSettingsCategory: getAdvancedSettingsCategory(state, Preferences.CATEGORY_ADVANCED_SETTINGS), sendOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', 'false'), codeBlockOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', 'true'), formatting: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true'), - joinLeave: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', 'true'), + joinLeave: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', enableJoinLeaveMessage.toString()), syncDrafts: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'sync_drafts', 'true'), currentUser: getCurrentUser(state), unreadScrollPosition: getUnreadScrollPositionPreference(state), diff --git a/webapp/channels/src/components/user_settings/advanced/join_leave_section/index.ts b/webapp/channels/src/components/user_settings/advanced/join_leave_section/index.ts index f9a15a3c05..101e1a74d9 100644 --- a/webapp/channels/src/components/user_settings/advanced/join_leave_section/index.ts +++ b/webapp/channels/src/components/user_settings/advanced/join_leave_section/index.ts @@ -4,8 +4,9 @@ import {bindActionCreators, Dispatch} from 'redux'; import {connect} from 'react-redux'; -import {GlobalState} from 'types/store/index.js'; +import {GlobalState} from 'types/store'; +import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {GenericAction} from 'mattermost-redux/types/actions.js'; import {savePreferences} from 'mattermost-redux/actions/preferences'; @@ -15,12 +16,15 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import JoinLeaveSection from './join_leave_section'; -function mapStateToProps(state: GlobalState) { +export function mapStateToProps(state: GlobalState) { + const config = getConfig(state); + const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true'; + const joinLeave = getPreference( state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, - 'true', + enableJoinLeaveMessage.toString(), ); return { diff --git a/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.test.tsx b/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.test.tsx index 55842d6b32..50a944bc51 100644 --- a/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.test.tsx +++ b/webapp/channels/src/components/user_settings/advanced/join_leave_section/join_leave_section.test.tsx @@ -3,6 +3,11 @@ import React from 'react'; import {shallow} from 'enzyme'; +import {GlobalState} from 'types/store'; + +import mergeObjects from 'packages/mattermost-redux/test/merge_objects'; +import {Preferences} from 'mattermost-redux/constants'; +import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; import {AdvancedSections} from 'utils/constants'; @@ -114,3 +119,92 @@ describe('components/user_settings/advanced/JoinLeaveSection', () => { expect(onUpdateSection).toBeCalledWith(AdvancedSections.JOIN_LEAVE); }); }); + +import {mapStateToProps} from './index'; + +describe('mapStateToProps', () => { + const currentUserId = 'user-id'; + + const initialState = { + currentUserId, + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, + preferences: { + myPreferences: {}, + }, + users: { + currentUserId, + profiles: { + [currentUserId]: { + id: currentUserId, + }, + }, + }, + + }, + } as unknown as GlobalState; + + test('configuration default to true', () => { + const props = mapStateToProps(initialState); + expect(props.joinLeave).toEqual('true'); + }); + + test('configuration default to false', () => { + const testState = mergeObjects(initialState, { + entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'false', + }, + }, + }, + }); + const props = mapStateToProps(testState); + expect(props.joinLeave).toEqual('false'); + }); + + test('user setting takes presidence', () => { + const testState = mergeObjects(initialState, { + entities: { + general: { + config: { + EnableJoinDefault: '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', + }, + }, + }, + }, + }); + const props = mapStateToProps(testState); + expect(props.joinLeave).toEqual('true'); + }); + + test('user setting takes presidence opposite', () => { + const testState = mergeObjects(initialState, { + entities: { + 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', + }, + }, + }, + }, + }); + const props = mapStateToProps(testState); + expect(props.joinLeave).toEqual('false'); + }); +}); diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index e305a613ac..889d1c8c81 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -2484,6 +2484,8 @@ "admin.team.customUserStatusesTitle": "Enable Custom Statuses: ", "admin.team.emailInvitationsDescription": "When true users can invite others to the system using email.", "admin.team.emailInvitationsTitle": "Enable Email Invitations: ", + "admin.team.enableJoinLeaveMessageDescription": "Choose the default configuration of system messages displayed when users join or leave channels. Users can override this default by configuring Join/Leave messages in Account Settings > Advanced.", + "admin.team.enableJoinLeaveMessageTitle": "Enable join/leave messages by default:", "admin.team.invalidateEmailInvitesDescription": "This will invalidate active email invitations that have not been accepted by the user. By default email invitations expire after 48 hours.", "admin.team.invalidateEmailInvitesFail": "Unable to invalidate pending email invites: {error}", "admin.team.invalidateEmailInvitesSuccess": "Pending email invitations invalidated successfully", 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 a48ee2bcea..b0a9865de5 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 @@ -33,12 +33,12 @@ describe('Actions.Posts', () => { general: { config: { CollapsedThreads: 'always_on', + EnableJoinLeaveMessageByDefault: 'true', }, }, }, }); }); - afterAll(() => { TestHelper.tearDown(); }); 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 4aaf2cacfd..f4da61f05b 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 @@ -43,6 +43,11 @@ describe('Selectors.Posts', () => { const testState = deepFreezeAndThrowOnMutation({ entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, users: { currentUserId: user1.id, profiles, @@ -802,6 +807,9 @@ describe('Selectors.Posts', () => { const state = { entities: { + general: { + config: {}, + }, users: { currentUserId: user1.id, profiles, @@ -833,6 +841,9 @@ describe('Selectors.Posts', () => { const state = { entities: { + general: { + config: {}, + }, users: { currentUserId: user1.id, profiles, @@ -864,6 +875,9 @@ describe('Selectors.Posts', () => { let state = { entities: { + general: { + config: {}, + }, users: { currentUserId: user1.id, profiles, @@ -983,6 +997,9 @@ describe('Selectors.Posts', () => { const state = { entities: { + general: { + config: {}, + }, users: { currentUserId: user1.id, profiles, @@ -2472,6 +2489,11 @@ describe('makeGetProfilesForThread', () => { const state = { entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: { 1001: {id: '1001', create_at: 1001, user_id: 'user1'}, @@ -2507,6 +2529,11 @@ describe('makeGetProfilesForThread', () => { const state = { entities: { + general: { + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, + }, posts: { posts: { 1001: {id: '1001', create_at: 1001, user_id: 'user1'}, diff --git a/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.test.ts b/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.test.ts index 2c8d12c461..c52b3beb52 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.test.ts @@ -24,6 +24,7 @@ import { makeGenerateCombinedPost, extractUserActivityData, START_OF_NEW_MESSAGES, + shouldShowJoinLeaveMessages, } from './post_list'; describe('makeFilterPostsAndAddSeparators', () => { @@ -35,7 +36,9 @@ describe('makeFilterPostsAndAddSeparators', () => { let state = { entities: { general: { - config: {}, + config: { + EnableJoinLeaveMessageByDefault: 'true', + }, }, posts: { posts: { @@ -1472,3 +1475,95 @@ describe('combineUserActivityData', () => { 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); + }); +}); diff --git a/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.ts b/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.ts index 73f293c0ca..6aa9afae6b 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/utils/post_list.ts @@ -10,6 +10,7 @@ import {makeGetPostsForIds, UserActivityPost} from 'mattermost-redux/selectors/e import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; +import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {createIdsSelector, memoizeResult} from 'mattermost-redux/utils/helpers'; import {isUserActivityPost, shouldFilterJoinLeavePost, isFromWebhook} from 'mattermost-redux/utils/post_utils'; @@ -25,8 +26,11 @@ export const START_OF_NEW_MESSAGES = 'start-of-new-messages'; 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, true); + return getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, enableJoinLeaveMessage); } interface PostFilterOptions { diff --git a/webapp/platform/types/src/config.ts b/webapp/platform/types/src/config.ts index 73a530bdf8..6851a1f5b7 100644 --- a/webapp/platform/types/src/config.ts +++ b/webapp/platform/types/src/config.ts @@ -70,6 +70,7 @@ export type ClientConfig = { EnableGifPicker: string; EnableGuestAccounts: string; EnableIncomingWebhooks: string; + EnableJoinLeaveMessageByDefault: string; EnableLatex: string; EnableInlineLatex: string; EnableLdap: string;