Add config setting to set default User Setting for filter join/leave message (#24047)

* initial submit, join/leave message

* i18n-extract

* additional changes for client config

* add unit tests

* fix unit tests

* change tests to use string not bool

* more unit test fixes

* add and fix unit tests

* revert package-lock

* update unit tests

* Update default_config.ts

* fix unit test

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Scott Bishel 2023-08-29 12:30:16 -06:00 committed by GitHub
parent fe4a77498a
commit f35291169c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 293 additions and 19 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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{}

View File

@ -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',

View File

@ -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),

View File

@ -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 {

View File

@ -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');
});
});

View File

@ -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",

View File

@ -33,12 +33,12 @@ describe('Actions.Posts', () => {
general: {
config: {
CollapsedThreads: 'always_on',
EnableJoinLeaveMessageByDefault: 'true',
},
},
},
});
});
afterAll(() => {
TestHelper.tearDown();
});

View File

@ -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'},

View File

@ -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);
});
});

View File

@ -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 {

View File

@ -70,6 +70,7 @@ export type ClientConfig = {
EnableGifPicker: string;
EnableGuestAccounts: string;
EnableIncomingWebhooks: string;
EnableJoinLeaveMessageByDefault: string;
EnableLatex: string;
EnableInlineLatex: string;
EnableLdap: string;