You've already forked focalboard
							
							
				mirror of
				https://github.com/mattermost/focalboard.git
				synced 2025-10-31 00:17:42 +02:00 
			
		
		
		
	User updates (#3244)
* retrieve additional user fields * implement config setting * cleanup * fix tests * remove unused constant * fix more tests * add tests, cleanup * lint fixes * revert bad lint fixes * more merge fixes * update to use personal setting * add user settings * update package-lock.json * lint fixes * npm audit fix * update tests * Revert "lint fixes" This reverts commit6a50d335ca. * Revert "update package-lock.json" This reverts commit1117e557e4. * Revert "npm audit fix" This reverts commit77ea931c77. * Revert "Revert "lint fixes"" This reverts commit3ebd81b9fa. * restore to original * fix for empty prefs * fix bad merge Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
		| @@ -96,6 +96,7 @@ func (p *Plugin) OnConfigurationChange() error { | ||||
| 	} | ||||
| 	p.server.Config().EnableDataRetention = enableBoardsDeletion | ||||
| 	p.server.Config().DataRetentionDays = *mmconfig.DataRetentionSettings.BoardsRetentionDays | ||||
| 	p.server.Config().TeammateNameDisplay = *mmconfig.TeamSettings.TeammateNameDisplay | ||||
|  | ||||
| 	p.server.UpdateAppConfig() | ||||
| 	p.wsPluginAdapter.BroadcastConfigChange(*p.server.App().GetClientConfig()) | ||||
|   | ||||
| @@ -62,11 +62,16 @@ func TestOnConfigurationChange(t *testing.T) { | ||||
| 	baseDataRetentionSettings := &serverModel.DataRetentionSettings{ | ||||
| 		BoardsRetentionDays: &intRef, | ||||
| 	} | ||||
| 	usernameRef := "username" | ||||
| 	baseTeamSettings := &serverModel.TeamSettings{ | ||||
| 		TeammateNameDisplay: &usernameRef, | ||||
| 	} | ||||
|  | ||||
| 	baseConfig := &serverModel.Config{ | ||||
| 		FeatureFlags:          baseFeatureFlags, | ||||
| 		PluginSettings:        *basePluginSettings, | ||||
| 		DataRetentionSettings: *baseDataRetentionSettings, | ||||
| 		TeamSettings:          *baseTeamSettings, | ||||
| 	} | ||||
|  | ||||
| 	t.Run("Test Load Plugin Success", func(t *testing.T) { | ||||
|   | ||||
| @@ -261,6 +261,7 @@ func (p *Plugin) createBoardsConfig(mmconfig mmModel.Config, baseURL string, ser | ||||
| 		NotifyFreqBoardSeconds:   getPluginSettingInt(mmconfig, notifyFreqBoardSecondsKey, 86400), | ||||
| 		EnableDataRetention:      enableBoardsDeletion, | ||||
| 		DataRetentionDays:        *mmconfig.DataRetentionSettings.BoardsRetentionDays, | ||||
| 		TeammateNameDisplay:      *mmconfig.TeamSettings.TeammateNameDisplay, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,10 @@ func TestSetConfiguration(t *testing.T) { | ||||
| 	baseDataRetentionSettings := &model.DataRetentionSettings{ | ||||
| 		BoardsRetentionDays: &days, | ||||
| 	} | ||||
| 	usernameRef := "username" | ||||
| 	baseTeamSettings := &model.TeamSettings{ | ||||
| 		TeammateNameDisplay: &usernameRef, | ||||
| 	} | ||||
|  | ||||
| 	baseConfig := &model.Config{ | ||||
| 		FeatureFlags:          baseFeatureFlags, | ||||
| @@ -75,6 +79,7 @@ func TestSetConfiguration(t *testing.T) { | ||||
| 		SqlSettings:           *baseSQLSettings, | ||||
| 		FileSettings:          *baseFileSettings, | ||||
| 		DataRetentionSettings: *baseDataRetentionSettings, | ||||
| 		TeamSettings:          *baseTeamSettings, | ||||
| 	} | ||||
|  | ||||
| 	t.Run("test enable telemetry", func(t *testing.T) { | ||||
|   | ||||
| @@ -12,6 +12,8 @@ import {GlobalState} from 'mattermost-redux/types/store' | ||||
| import {selectTeam} from 'mattermost-redux/actions/teams' | ||||
|  | ||||
| import {SuiteWindow} from '../../../webapp/src/types/index' | ||||
| import {UserSettings} from '../../../webapp/src/userSettings' | ||||
|  | ||||
|  | ||||
| const windowAny = (window as SuiteWindow) | ||||
| windowAny.baseURL = '/plugins/focalboard' | ||||
| @@ -182,6 +184,7 @@ export default class Plugin { | ||||
|  | ||||
|         this.registry = registry | ||||
|  | ||||
|         UserSettings.nameFormat = mmStore.getState().entities.preferences?.myPreferences['display_settings--name_format']?.value || null | ||||
|         let theme = mmStore.getState().entities.preferences.myPreferences.theme | ||||
|         setMattermostTheme(theme) | ||||
|         let lastViewedChannel = mmStore.getState().entities.channels.currentChannelId | ||||
| @@ -326,6 +329,9 @@ export default class Plugin { | ||||
|                         setMattermostTheme(JSON.parse(preference.value)) | ||||
|                         theme = preference.value | ||||
|                     } | ||||
|                     if(preference.category === 'display_settings' && preference.name === 'name_format'){ | ||||
|                         UserSettings.nameFormat = preference.value | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|   | ||||
| @@ -1100,7 +1100,6 @@ func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { | ||||
| 		a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	jsonBytesResponse(w, http.StatusOK, userData) | ||||
|  | ||||
| 	auditRec.AddMeta("userID", user.ID) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ func (a *App) GetClientConfig() *model.ClientConfig { | ||||
| 		Telemetry:                a.config.Telemetry, | ||||
| 		TelemetryID:              a.config.TelemetryID, | ||||
| 		EnablePublicSharedBoards: a.config.EnablePublicSharedBoards, | ||||
| 		TeammateNameDisplay:      a.config.TeammateNameDisplay, | ||||
| 		FeatureFlags:             a.config.FeatureFlags, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -19,6 +19,7 @@ func TestGetClientConfig(t *testing.T) { | ||||
| 		newConfiguration.FeatureFlags = make(map[string]string) | ||||
| 		newConfiguration.FeatureFlags["BoardsFeature1"] = "true" | ||||
| 		newConfiguration.FeatureFlags["BoardsFeature2"] = "true" | ||||
| 		newConfiguration.TeammateNameDisplay = "username" | ||||
| 		th.App.SetConfig(&newConfiguration) | ||||
|  | ||||
| 		clientConfig := th.App.GetClientConfig() | ||||
| @@ -26,5 +27,6 @@ func TestGetClientConfig(t *testing.T) { | ||||
| 		require.True(t, clientConfig.Telemetry) | ||||
| 		require.Equal(t, "abcde", clientConfig.TelemetryID) | ||||
| 		require.Equal(t, 2, len(clientConfig.FeatureFlags)) | ||||
| 		require.Equal(t, "username", clientConfig.TeammateNameDisplay) | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ type ClientConfig struct { | ||||
| 	// required: true | ||||
| 	EnablePublicSharedBoards bool `json:"enablePublicSharedBoards"` | ||||
|  | ||||
| 	// Is public shared boards enabled | ||||
| 	// required: true | ||||
| 	TeammateNameDisplay string `json:"teammateNameDisplay"` | ||||
|  | ||||
| 	// The server feature flags | ||||
| 	// required: true | ||||
| 	FeatureFlags map[string]string `json:"featureFlags"` | ||||
|   | ||||
| @@ -26,6 +26,13 @@ type User struct { | ||||
| 	// required: true | ||||
| 	Email string `json:"-"` | ||||
|  | ||||
| 	// The user's nickname | ||||
| 	Nickname string `json:"nickname"` | ||||
| 	// The user's first name | ||||
| 	FirstName string `json:"firstname"` | ||||
| 	// The user's last name | ||||
| 	LastName string `json:"lastname"` | ||||
|  | ||||
| 	// swagger:ignore | ||||
| 	Password string `json:"-"` | ||||
|  | ||||
|   | ||||
| @@ -52,6 +52,7 @@ type Configuration struct { | ||||
| 	FeatureFlags             map[string]string `json:"featureFlags" mapstructure:"featureFlags"` | ||||
| 	EnableDataRetention      bool              `json:"enable_data_retention" mapstructure:"enable_data_retention"` | ||||
| 	DataRetentionDays        int               `json:"data_retention_days" mapstructure:"data_retention_days"` | ||||
| 	TeammateNameDisplay      string            `json:"teammate_name_display" mapstructure:"teammateNameDisplay"` | ||||
|  | ||||
| 	AuthMode string `json:"authMode" mapstructure:"authMode"` | ||||
|  | ||||
| @@ -100,6 +101,7 @@ func ReadConfigFile(configFilePath string) (*Configuration, error) { | ||||
| 	viper.SetDefault("EnableDataRetention", false) | ||||
| 	viper.SetDefault("DataRetentionDays", 365) // 1 year is default | ||||
| 	viper.SetDefault("PrometheusAddress", "") | ||||
| 	viper.SetDefault("TeammateNameDisplay", "username") | ||||
|  | ||||
| 	err := viper.ReadInConfig() // Find and read the config file | ||||
| 	if err != nil {             // Handle errors reading the config file | ||||
|   | ||||
| @@ -266,7 +266,7 @@ func (s *MattermostAuthLayer) getQueryBuilder() sq.StatementBuilderType { | ||||
|  | ||||
| func (s *MattermostAuthLayer) GetUsersByTeam(teamID string) ([]*model.User, error) { | ||||
| 	query := s.getQueryBuilder(). | ||||
| 		Select("u.id", "u.username", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", | ||||
| 		Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", | ||||
| 			"u.DeleteAt as delete_at", "b.UserId IS NOT NULL AS is_bot"). | ||||
| 		From("Users as u"). | ||||
| 		Join("TeamMembers as tm ON tm.UserID = u.ID"). | ||||
| @@ -291,7 +291,7 @@ func (s *MattermostAuthLayer) GetUsersByTeam(teamID string) ([]*model.User, erro | ||||
|  | ||||
| func (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery string) ([]*model.User, error) { | ||||
| 	query := s.getQueryBuilder(). | ||||
| 		Select("u.id", "u.username", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", | ||||
| 		Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", | ||||
| 			"u.DeleteAt as delete_at", "b.UserId IS NOT NULL AS is_bot"). | ||||
| 		From("Users as u"). | ||||
| 		Join("TeamMembers as tm ON tm.UserID = u.id"). | ||||
| @@ -332,6 +332,10 @@ func (s *MattermostAuthLayer) usersFromRows(rows *sql.Rows) ([]*model.User, erro | ||||
| 		err := rows.Scan( | ||||
| 			&user.ID, | ||||
| 			&user.Username, | ||||
| 			&user.Email, | ||||
| 			&user.Nickname, | ||||
| 			&user.FirstName, | ||||
| 			&user.LastName, | ||||
| 			&propsBytes, | ||||
| 			&user.CreateAt, | ||||
| 			&user.UpdateAt, | ||||
| @@ -385,6 +389,9 @@ func mmUserToFbUser(mmUser *mmModel.User) model.User { | ||||
| 		Username:    mmUser.Username, | ||||
| 		Email:       mmUser.Email, | ||||
| 		Password:    mmUser.Password, | ||||
| 		Nickname:    mmUser.Nickname, | ||||
| 		FirstName:   mmUser.FirstName, | ||||
| 		LastName:    mmUser.LastName, | ||||
| 		MfaSecret:   mmUser.MfaSecret, | ||||
| 		AuthService: mmUser.AuthService, | ||||
| 		AuthData:    authData, | ||||
|   | ||||
| @@ -61,6 +61,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => { | ||||
|         id: 'user-id-1', | ||||
|         username: 'username_1', | ||||
|         email: '', | ||||
|         nickname: '', | ||||
|         firstname: '',  | ||||
|         lastname: '', | ||||
|         props: {}, | ||||
|         create_at: 0, | ||||
|         update_at: 0, | ||||
|   | ||||
| @@ -96,12 +96,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => { | ||||
|     } | ||||
|  | ||||
|     const me: IUser = { | ||||
|         id: 'user-id-1', | ||||
|         username: 'username_1', | ||||
|         email: '', | ||||
|         props: {}, | ||||
|         create_at: 0, | ||||
|         update_at: 0, | ||||
|         id: 'user-id-1',  | ||||
|         username: 'username_1',  | ||||
|         nickname: '', | ||||
|         firstname: '',  | ||||
|         lastname: '', | ||||
|         email: '',  | ||||
|         props: {},  | ||||
|         create_at: 0,  | ||||
|         update_at: 0,  | ||||
|         is_bot: false, | ||||
|         roles: 'system_user', | ||||
|     } | ||||
|   | ||||
| @@ -62,7 +62,10 @@ describe('components/content/TextElement', () => { | ||||
|             boards: { | ||||
|                 [board1.id]: board1, | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: {}, | ||||
|         }, | ||||
|     } | ||||
|     const store = mockStateStore([], state) | ||||
|  | ||||
|   | ||||
| @@ -83,7 +83,10 @@ describe('components/contentBlock', () => { | ||||
|             boards: { | ||||
|                 [board1.id]: board1, | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: {}, | ||||
|         }, | ||||
|     } | ||||
|     const store = mockStateStore([], state) | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,10 @@ describe('components/markdownEditor', () => { | ||||
|             boards: { | ||||
|                 [board1.id]: board1, | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: {}, | ||||
|         }, | ||||
|     } | ||||
|     const store = mockStateStore([], state) | ||||
|     test('should match snapshot', async () => { | ||||
|   | ||||
| @@ -27,6 +27,9 @@ const Entry = (props: EntryComponentProps): ReactElement => { | ||||
|                     {mention.name} | ||||
|                     {BotBadge && <BotBadge show={mention.is_bot}/>} | ||||
|                 </div> | ||||
|                 <div className={theme?.mentionSuggestionsEntryText}> | ||||
|                     {mention.displayName} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     ) | ||||
|   | ||||
| @@ -21,9 +21,13 @@ import createLiveMarkdownPlugin from '../live-markdown-plugin/liveMarkdownPlugin | ||||
|  | ||||
| import './markdownEditorInput.scss' | ||||
|  | ||||
| import {BoardTypeOpen} from "../../blocks/board" | ||||
| import {getCurrentBoard} from "../../store/boards" | ||||
| import octoClient from "../../octoClient" | ||||
| import {BoardTypeOpen} from '../../blocks/board' | ||||
| import {getCurrentBoard} from '../../store/boards' | ||||
| import octoClient from '../../octoClient' | ||||
|  | ||||
| import {Utils} from '../../utils' | ||||
| import {ClientConfig} from '../../config/clientConfig' | ||||
| import {getClientConfig} from '../../store/clientConfig' | ||||
|  | ||||
| import Entry from './entryComponent/entryComponent' | ||||
|  | ||||
| @@ -33,6 +37,7 @@ type MentionUser = { | ||||
|     name: string | ||||
|     avatar: string | ||||
|     is_bot: boolean | ||||
|     displayName: string | ||||
| } | ||||
|  | ||||
| type Props = { | ||||
| @@ -48,6 +53,7 @@ const MarkdownEditorInput = (props: Props): ReactElement => { | ||||
|     const {onChange, onFocus, onBlur, initialText, id, isEditing} = props | ||||
|     const boardUsers = useAppSelector<IUser[]>(getBoardUsersList) | ||||
|     const board = useAppSelector(getCurrentBoard) | ||||
|     const clientConfig = useAppSelector<ClientConfig>(getClientConfig) | ||||
|     const ref = useRef<Editor>(null) | ||||
|  | ||||
|     const [suggestions, setSuggestions] = useState<Array<MentionUser>>([]) | ||||
| @@ -65,7 +71,8 @@ const MarkdownEditorInput = (props: Props): ReactElement => { | ||||
|             (user) => ({ | ||||
|                 name: user.username, | ||||
|                 avatar: `${imageURLForUser ? imageURLForUser(user.id) : ''}`, | ||||
|                 is_bot: user.is_bot} | ||||
|                 is_bot: user.is_bot, | ||||
|                 displayName: Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)} | ||||
|             )) | ||||
|         setSuggestions(mentions) | ||||
|     } | ||||
|   | ||||
| @@ -41,6 +41,9 @@ describe('components/messages/CloudMessage', () => { | ||||
|             id: 'user-id-1', | ||||
|             username: 'username_1', | ||||
|             email: '', | ||||
|             nickname: '', | ||||
|             firstname: '',  | ||||
|             lastname: '', | ||||
|             props: {}, | ||||
|             create_at: 0, | ||||
|             update_at: 0, | ||||
| @@ -70,6 +73,9 @@ describe('components/messages/CloudMessage', () => { | ||||
|             id: 'user-id-1', | ||||
|             username: 'username_1', | ||||
|             email: '', | ||||
|             nickname: '', | ||||
|             firstname: '',  | ||||
|             lastname: '', | ||||
|             props: { | ||||
|                 focalboard_cloudMessageCanceled: 'true', | ||||
|             }, | ||||
| @@ -101,6 +107,9 @@ describe('components/messages/CloudMessage', () => { | ||||
|             id: 'user-id-1', | ||||
|             username: 'username_1', | ||||
|             email: '', | ||||
|             nickname: '', | ||||
|             firstname: '',  | ||||
|             lastname: '', | ||||
|             props: {}, | ||||
|             create_at: 0, | ||||
|             update_at: 0, | ||||
| @@ -138,6 +147,9 @@ describe('components/messages/CloudMessage', () => { | ||||
|             id: 'single-user', | ||||
|             username: 'single-user', | ||||
|             email: 'single-user', | ||||
|             nickname: '', | ||||
|             firstname: '',  | ||||
|             lastname: '', | ||||
|             props: {}, | ||||
|             create_at: 0, | ||||
|             update_at: Date.now() - (1000 * 60 * 60 * 24), //24 hours, | ||||
|   | ||||
| @@ -24,6 +24,11 @@ describe('components/properties/createdBy', () => { | ||||
|                     'user-id-1': {username: 'username_1'} as IUser, | ||||
|                 }, | ||||
|             }, | ||||
|             clientConfig: { | ||||
|                 value: { | ||||
|                     teammateNameDisplay: 'username', | ||||
|                 }, | ||||
|             }, | ||||
|         }) | ||||
|  | ||||
|         const component = ( | ||||
|   | ||||
| @@ -42,6 +42,11 @@ describe('components/properties/lastModifiedBy', () => { | ||||
|                     [card.id]: [comment], | ||||
|                 }, | ||||
|             }, | ||||
|             clientConfig: { | ||||
|                 value: { | ||||
|                     teammateNameDisplay: 'username', | ||||
|                 }, | ||||
|             }, | ||||
|         }) | ||||
|  | ||||
|         const component = ( | ||||
|   | ||||
| @@ -32,6 +32,11 @@ describe('components/properties/user', () => { | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: { | ||||
|                 teammateNameDisplay: 'username', | ||||
|             }, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     test('not readonly not existing user', async () => { | ||||
| @@ -110,8 +115,7 @@ describe('components/properties/user', () => { | ||||
|                 <UserProperty | ||||
|                     value={'user-id-1'} | ||||
|                     readonly={false} | ||||
|                     onChange={() => { | ||||
|                     }} | ||||
|                     onChange={() => {}} | ||||
|                 /> | ||||
|             </ReduxProvider>, | ||||
|         ) | ||||
|   | ||||
| @@ -5,12 +5,17 @@ import React from 'react' | ||||
| import Select from 'react-select' | ||||
| import {CSSObject} from '@emotion/serialize' | ||||
|  | ||||
| import {Utils} from '../../../utils' | ||||
|  | ||||
| import {IUser} from '../../../user' | ||||
|  | ||||
| import {getBoardUsersList, getBoardUsers} from '../../../store/users' | ||||
| import {useAppSelector} from '../../../store/hooks' | ||||
|  | ||||
| import './user.scss' | ||||
| import {getSelectBaseStyle} from '../../../theme' | ||||
| import {ClientConfig} from '../../../config/clientConfig' | ||||
| import {getClientConfig} from '../../../store/clientConfig' | ||||
| import {propertyValueClassName} from '../../propertyValueUtils' | ||||
|  | ||||
| const imageURLForUser = (window as any).Components?.imageURLForUser | ||||
| @@ -53,26 +58,28 @@ const selectStyles = { | ||||
|     }), | ||||
| } | ||||
|  | ||||
| const formatOptionLabel = (user: any) => { | ||||
|     let profileImg | ||||
|     if (imageURLForUser) { | ||||
|         profileImg = imageURLForUser(user.id) | ||||
| const UserProperty = (props: Props): JSX.Element => { | ||||
|     const clientConfig = useAppSelector<ClientConfig>(getClientConfig) | ||||
|  | ||||
|     const formatOptionLabel = (user: any) => { | ||||
|         let profileImg | ||||
|         if (imageURLForUser) { | ||||
|             profileImg = imageURLForUser(user.id) | ||||
|         } | ||||
|      | ||||
|         return ( | ||||
|             <div className='UserProperty-item'> | ||||
|                 {profileImg && ( | ||||
|                     <img | ||||
|                         alt='UserProperty-avatar' | ||||
|                         src={profileImg} | ||||
|                     /> | ||||
|                 )} | ||||
|                 {Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)} | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div className='UserProperty-item'> | ||||
|             {profileImg && ( | ||||
|                 <img | ||||
|                     alt='UserProperty-avatar' | ||||
|                     src={profileImg} | ||||
|                 /> | ||||
|             )} | ||||
|             {user.username} | ||||
|         </div> | ||||
|     ) | ||||
| } | ||||
|  | ||||
| const UserProperty = (props: Props): JSX.Element => { | ||||
|     const boardUsersById = useAppSelector<{[key:string]: IUser}>(getBoardUsers) | ||||
|  | ||||
|     const user = boardUsersById[props.value] | ||||
|   | ||||
| @@ -101,6 +101,9 @@ const me: IUser = { | ||||
|     id: 'user-id-1', | ||||
|     username: 'username_1', | ||||
|     email: '', | ||||
|     nickname: '', | ||||
|     firstname: '', | ||||
|     lastname: '', | ||||
|     props: {}, | ||||
|     create_at: 0, | ||||
|     update_at: 0, | ||||
| @@ -157,6 +160,7 @@ describe('src/components/shareBoard/shareBoard', () => { | ||||
|                 telemetry: true, | ||||
|                 telemetryid: 'telemetry', | ||||
|                 enablePublicSharedBoards: true, | ||||
|                 teammateNameDisplay: 'username', | ||||
|                 featureFlags: {}, | ||||
|             }, | ||||
|         }, | ||||
| @@ -479,6 +483,7 @@ describe('src/components/shareBoard/shareBoard', () => { | ||||
|         } | ||||
|         mockedOctoClient.getSharing.mockResolvedValue(sharing) | ||||
|         mockedUtils.isFocalboardPlugin.mockReturnValue(true) | ||||
|         mockedUtils.getUserDisplayName.mockImplementation((u) => u.username) | ||||
|  | ||||
|         const users:IUser[] = [ | ||||
|             {id: 'userid1', username: 'username_1'} as IUser, | ||||
|   | ||||
| @@ -13,6 +13,9 @@ import {getCurrentBoard, getCurrentBoardMembers} from '../../store/boards' | ||||
| import {Channel, ChannelTypeOpen, ChannelTypePrivate} from '../../store/channels' | ||||
| import {getMe, getBoardUsersList} from '../../store/users' | ||||
|  | ||||
| import {ClientConfig} from '../../config/clientConfig' | ||||
| import {getClientConfig} from '../../store/clientConfig' | ||||
|  | ||||
| import {Utils, IDType} from '../../utils' | ||||
| import Tooltip from '../../widgets/tooltip' | ||||
| import mutator from '../../mutator' | ||||
| @@ -100,6 +103,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element { | ||||
|     const [showLinkChannelConfirmation, setShowLinkChannelConfirmation] = useState<Channel|null>(null) | ||||
|     const [sharing, setSharing] = useState<ISharing|undefined>(undefined) | ||||
|     const [selectedUser, setSelectedUser] = useState<IUser|Channel|null>(null) | ||||
|     const clientConfig = useAppSelector<ClientConfig>(getClientConfig) | ||||
|  | ||||
|     // members of the current board | ||||
|     const members = useAppSelector<{[key: string]: BoardMember}>(getCurrentBoardMembers) | ||||
| @@ -293,7 +297,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element { | ||||
|                         /> | ||||
|                     } | ||||
|                     <div className='ml-3'> | ||||
|                         <strong>{user.username}</strong> | ||||
|                         <strong>{Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)}</strong> | ||||
|                         <strong className='ml-2 text-light'>{`@${user.username}`}</strong> | ||||
|                     </div> | ||||
|                 </div> | ||||
| @@ -390,6 +394,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element { | ||||
|                             key={user.id} | ||||
|                             user={user} | ||||
|                             member={members[user.id]} | ||||
|                             teammateNameDisplay={me?.props?.teammateNameDisplay || clientConfig.teammateNameDisplay} | ||||
|                             onDeleteBoardMember={onDeleteBoardMember} | ||||
|                             onUpdateBoardMember={onUpdateBoardMember} | ||||
|                             isMe={user.id === me?.id} | ||||
|   | ||||
| @@ -21,13 +21,14 @@ type Props = { | ||||
|     user: IUser | ||||
|     member: BoardMember | ||||
|     isMe: boolean | ||||
|     teammateNameDisplay: string, | ||||
|     onDeleteBoardMember: (member: BoardMember) => void | ||||
|     onUpdateBoardMember: (member: BoardMember, permission: string) => void | ||||
| } | ||||
|  | ||||
| const UserPermissionsRow = (props: Props): JSX.Element => { | ||||
|     const intl = useIntl() | ||||
|     const {user, member, isMe} = props | ||||
|     const {user, member, isMe, teammateNameDisplay} = props | ||||
|     let currentRole = 'Viewer' | ||||
|     if (member.schemeAdmin) { | ||||
|         currentRole = 'Admin' | ||||
| @@ -47,7 +48,7 @@ const UserPermissionsRow = (props: Props): JSX.Element => { | ||||
|                     /> | ||||
|                 } | ||||
|                 <div className='ml-3'> | ||||
|                     <strong>{user.username}</strong> | ||||
|                     <strong>{Utils.getUserDisplayName(user, teammateNameDisplay)}</strong> | ||||
|                     <strong className='ml-2 text-light'>{`@${user.username}`}</strong> | ||||
|                     {isMe && <strong className='ml-2 text-light'>{intl.formatMessage({id: 'ShareBoard.userPermissionsYouText', defaultMessage: '(You)'})}</strong>} | ||||
|                 </div> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
|  | ||||
| exports[`components/table/Table extended should match snapshot with CreatedBy 1`] = ` | ||||
| exports[`components/table/Table extended should match snapshot with CreatedAt 1`] = ` | ||||
| <div> | ||||
|   <div | ||||
|     class="Table" | ||||
| @@ -460,7 +460,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 1` | ||||
| </div> | ||||
| `; | ||||
|  | ||||
| exports[`components/table/Table extended should match snapshot with CreatedBy 2`] = ` | ||||
| exports[`components/table/Table extended should match snapshot with CreatedBy 1`] = ` | ||||
| <div> | ||||
|   <div | ||||
|     class="Table" | ||||
|   | ||||
| @@ -301,9 +301,14 @@ describe('components/table/Table extended', () => { | ||||
|                 board_id: {userId: 'user_id_1', schemeAdmin: true}, | ||||
|             }, | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: { | ||||
|                 teammateNameDisplay: 'username', | ||||
|             }, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     test('should match snapshot with CreatedBy', async () => { | ||||
|     test('should match snapshot with CreatedAt', async () => { | ||||
|         const board = TestBlockFactory.createBoard() | ||||
|  | ||||
|         const dateCreatedId = Utils.createGuid(IDType.User) | ||||
|   | ||||
| @@ -53,6 +53,9 @@ describe('components/viewTitle', () => { | ||||
|                 [board.id]: {userId: 'user_id_1', schemeAdmin: true}, | ||||
|             }, | ||||
|         }, | ||||
|         clientConfig: { | ||||
|             value: {}, | ||||
|         }, | ||||
|     } | ||||
|     const store = mockStateStore([], state) | ||||
|  | ||||
|   | ||||
| @@ -76,6 +76,9 @@ const me: IUser = { | ||||
|     id: 'user-id-1', | ||||
|     username: 'username_1', | ||||
|     email: '', | ||||
|     nickname: '', | ||||
|     firstname: '', | ||||
|     lastname: '', | ||||
|     props: {}, | ||||
|     create_at: 0, | ||||
|     update_at: 0, | ||||
| @@ -150,6 +153,7 @@ describe('src/components/workspace', () => { | ||||
|                 telemetry: true, | ||||
|                 telemetryid: 'telemetry', | ||||
|                 enablePublicSharedBoards: true, | ||||
|                 teammateNameDisplay: 'username', | ||||
|                 featureFlags: {}, | ||||
|             }, | ||||
|         }, | ||||
| @@ -272,6 +276,7 @@ describe('src/components/workspace', () => { | ||||
|                     telemetry: true, | ||||
|                     telemetryid: 'telemetry', | ||||
|                     enablePublicSharedBoards: true, | ||||
|                     teammateNameDisplay: 'username', | ||||
|                     featureFlags: {}, | ||||
|                 }, | ||||
|             }, | ||||
| @@ -310,6 +315,9 @@ describe('src/components/workspace', () => { | ||||
|                     id: 'user-id-1', | ||||
|                     username: 'username_1', | ||||
|                     email: '', | ||||
|                     nickname: '', | ||||
|                     firstname: '',  | ||||
|                     lastname: '', | ||||
|                     props: { | ||||
|                         focalboard_welcomePageViewed: '1', | ||||
|                         focalboard_onboardingTourStarted: true, | ||||
| @@ -361,6 +369,7 @@ describe('src/components/workspace', () => { | ||||
|                     telemetry: true, | ||||
|                     telemetryid: 'telemetry', | ||||
|                     enablePublicSharedBoards: true, | ||||
|                     teammateNameDisplay: 'username', | ||||
|                     featureFlags: {}, | ||||
|                 }, | ||||
|             }, | ||||
| @@ -410,6 +419,9 @@ describe('src/components/workspace', () => { | ||||
|                     id: 'user-id-1', | ||||
|                     username: 'username_1', | ||||
|                     email: '', | ||||
|                     nickname: '', | ||||
|                     firstname: '',  | ||||
|                     lastname: '', | ||||
|                     props: { | ||||
|                         focalboard_welcomePageViewed: '1', | ||||
|                         focalboard_onboardingTourStarted: true, | ||||
| @@ -461,6 +473,7 @@ describe('src/components/workspace', () => { | ||||
|                     telemetry: true, | ||||
|                     telemetryid: 'telemetry', | ||||
|                     enablePublicSharedBoards: true, | ||||
|                     teammateNameDisplay: 'username', | ||||
|                     featureFlags: {}, | ||||
|                 }, | ||||
|             }, | ||||
| @@ -515,6 +528,9 @@ describe('src/components/workspace', () => { | ||||
|                     id: 'user-id-1', | ||||
|                     username: 'username_1', | ||||
|                     email: '', | ||||
|                     nickname: '', | ||||
|                     firstname: '',  | ||||
|                     lastname: '', | ||||
|                     props: { | ||||
|                         focalboard_welcomePageViewed: '1', | ||||
|                         focalboard_onboardingTourStarted: true, | ||||
| @@ -566,6 +582,7 @@ describe('src/components/workspace', () => { | ||||
|                     telemetry: true, | ||||
|                     telemetryid: 'telemetry', | ||||
|                     enablePublicSharedBoards: true, | ||||
|                     teammateNameDisplay: 'username', | ||||
|                     featureFlags: {}, | ||||
|                 }, | ||||
|             }, | ||||
|   | ||||
| @@ -6,4 +6,5 @@ export type ClientConfig = { | ||||
|     telemetryid: string, | ||||
|     enablePublicSharedBoards: boolean, | ||||
|     featureFlags: Record<string, string>, | ||||
|     teammateNameDisplay: string, | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,8 @@ import {ClientConfig} from '../config/clientConfig' | ||||
|  | ||||
| import {default as client} from '../octoClient' | ||||
|  | ||||
| import {ShowUsername} from '../utils' | ||||
|  | ||||
| import {RootState} from './index' | ||||
|  | ||||
| export const fetchClientConfig = createAsyncThunk( | ||||
| @@ -16,7 +18,7 @@ export const fetchClientConfig = createAsyncThunk( | ||||
|  | ||||
| const clientConfigSlice = createSlice({ | ||||
|     name: 'config', | ||||
|     initialState: {value: {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, featureFlags: {}}} as {value: ClientConfig}, | ||||
|     initialState: {value: {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, teammateNameDisplay: ShowUsername, featureFlags: {}}} as {value: ClientConfig}, | ||||
|     reducers: { | ||||
|         setClientConfig: (state, action: PayloadAction<ClientConfig>) => { | ||||
|             state.value = action.payload | ||||
| @@ -24,7 +26,7 @@ const clientConfigSlice = createSlice({ | ||||
|     }, | ||||
|     extraReducers: (builder) => { | ||||
|         builder.addCase(fetchClientConfig.fulfilled, (state, action) => { | ||||
|             state.value = action.payload || {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, featureFlags: {}} | ||||
|             state.value = action.payload || {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, teammateNameDisplay: ShowUsername, featureFlags: {}} | ||||
|         }) | ||||
|     }, | ||||
| }) | ||||
| @@ -35,4 +37,3 @@ export const {reducer} = clientConfigSlice | ||||
| export function getClientConfig(state: RootState): ClientConfig { | ||||
|     return state.clientConfig.value | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -187,6 +187,9 @@ class TestBlockFactory { | ||||
|             id: 'user-id-1', | ||||
|             username: 'Dwight Schrute', | ||||
|             email: 'dwight.schrute@dundermifflin.com', | ||||
|             nickname: '',  | ||||
|             firstname: '',  | ||||
|             lastname: '', | ||||
|             props: {}, | ||||
|             create_at: Date.now(), | ||||
|             update_at: Date.now(), | ||||
|   | ||||
| @@ -5,6 +5,9 @@ interface IUser { | ||||
|     id: string, | ||||
|     username: string, | ||||
|     email: string, | ||||
|     nickname: string, | ||||
|     firstname: string, | ||||
|     lastname: string, | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|     props: Record<string, any>, | ||||
|     create_at: number, | ||||
|   | ||||
| @@ -17,7 +17,8 @@ export enum UserSettingKey { | ||||
|     RandomIcons = 'randomIcons', | ||||
|     MobileWarningClosed = 'mobileWarningClosed', | ||||
|     WelcomePageViewed = 'welcomePageViewed', | ||||
|     HideCloudMessage = 'hideCloudMessage' | ||||
|     HideCloudMessage = 'hideCloudMessage', | ||||
|     NameFormat = 'nameFormat' | ||||
| } | ||||
|  | ||||
| export class UserSettings { | ||||
| @@ -155,6 +156,15 @@ export class UserSettings { | ||||
|     static set hideCloudMessage(newValue: boolean) { | ||||
|         localStorage.setItem(UserSettingKey.HideCloudMessage, JSON.stringify(newValue)) | ||||
|     } | ||||
|  | ||||
|     static get nameFormat(): string | null { | ||||
|         return UserSettings.get(UserSettingKey.NameFormat) | ||||
|     } | ||||
|  | ||||
|     static set nameFormat(newValue: string | null) { | ||||
|         UserSettings.set(UserSettingKey.NameFormat, newValue) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| export function exportUserSettingsBlob(): string { | ||||
|   | ||||
| @@ -7,7 +7,9 @@ import {createMemoryHistory} from "history" | ||||
|  | ||||
| import {match as routerMatch} from "react-router-dom" | ||||
|  | ||||
| import {Utils, IDType} from './utils' | ||||
| import {Utils, IDType, ShowFullName, ShowNicknameFullName, ShowUsername} from './utils' | ||||
| import {IUser} from './user' | ||||
|  | ||||
| import {IAppWindow} from './types' | ||||
|  | ||||
| declare let window: IAppWindow | ||||
| @@ -186,4 +188,49 @@ describe('utils', () => { | ||||
|             expect(history.push).toBeCalledWith('/team/team_id_1/board_id_2') | ||||
|         }) | ||||
|     }) | ||||
|  | ||||
|     describe('getUserDisplayName test', () => { | ||||
|         const user: IUser = { | ||||
|             id: 'user-id-1', | ||||
|             username: 'username_1', | ||||
|             email: 'test@email.com', | ||||
|             nickname: 'nickname', | ||||
|             firstname: 'firstname', | ||||
|             lastname: 'lastname', | ||||
|             props: {}, | ||||
|             create_at: 0, | ||||
|             update_at: 0, | ||||
|             is_bot: false, | ||||
|             roles: 'system_user', | ||||
|         } | ||||
|  | ||||
|         it('should display username, by default', () => { | ||||
|             const displayName = Utils.getUserDisplayName(user, '') | ||||
|             expect(displayName).toEqual('username_1') | ||||
|         }) | ||||
|         it('should display nickname', () => { | ||||
|             const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName) | ||||
|             expect(displayName).toEqual('nickname') | ||||
|         }) | ||||
|         it('should display fullname', () => { | ||||
|             const displayName = Utils.getUserDisplayName(user, ShowFullName) | ||||
|             expect(displayName).toEqual('firstname lastname') | ||||
|         }) | ||||
|         it('should display username', () => { | ||||
|             const displayName = Utils.getUserDisplayName(user, ShowUsername) | ||||
|             expect(displayName).toEqual('username_1') | ||||
|         }) | ||||
|         it('should display full name, no nickname', () => { | ||||
|             user.nickname = '' | ||||
|             const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName) | ||||
|             expect(displayName).toEqual('firstname lastname') | ||||
|         }) | ||||
|         it('should display username, no nickname, no full name', () => { | ||||
|             user.nickname = '' | ||||
|             user.firstname = '' | ||||
|             user.lastname = '' | ||||
|             const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName) | ||||
|             expect(displayName).toEqual('username_1') | ||||
|         }) | ||||
|     }) | ||||
| }) | ||||
|   | ||||
| @@ -8,6 +8,8 @@ import {generatePath, match as routerMatch} from "react-router-dom" | ||||
|  | ||||
| import {History} from "history" | ||||
|  | ||||
| import {IUser} from './user' | ||||
|  | ||||
| import {Block} from './blocks/block' | ||||
| import {Board as BoardType, BoardMember, createBoard} from './blocks/board' | ||||
| import {createBoardView} from './blocks/boardView' | ||||
| @@ -16,6 +18,7 @@ import {createCommentBlock} from './blocks/commentBlock' | ||||
| import {IAppWindow} from './types' | ||||
| import {ChangeHandlerType, WSMessage} from './wsclient' | ||||
| import {BoardCategoryWebsocketData, Category} from './store/sidebar' | ||||
| import {UserSettings} from './userSettings' | ||||
|  | ||||
| declare let window: IAppWindow | ||||
|  | ||||
| @@ -49,6 +52,10 @@ export const KeyCodes: Record<string, [string, number]> = { | ||||
|     COMPOSING: ['Composing', 229], | ||||
| } | ||||
|  | ||||
| export const ShowUsername = 'username' | ||||
| export const ShowNicknameFullName = 'nickname_full_name' | ||||
| export const ShowFullName         = 'full_name' | ||||
|  | ||||
| class Utils { | ||||
|     static createGuid(idType: IDType): string { | ||||
|         const data = Utils.randomArray(16) | ||||
| @@ -80,6 +87,45 @@ class Utils { | ||||
|         return imageURLForUser && userId ? imageURLForUser(userId) : defaultImageUrl | ||||
|     } | ||||
|  | ||||
|     static getUserDisplayName(user: IUser, configNameFormat: string): string { | ||||
|         let nameFormat = configNameFormat     | ||||
|         if(UserSettings.nameFormat){ | ||||
|             nameFormat=UserSettings.nameFormat | ||||
|         } | ||||
|      | ||||
|         // default nameFormat = 'username' | ||||
|         let displayName = user.username | ||||
|      | ||||
|         if (nameFormat === ShowNicknameFullName) { | ||||
|             if( user.nickname != '') { | ||||
|                 displayName = user.nickname | ||||
|             } else { | ||||
|                 const fullName = Utils.getFullName(user) | ||||
|                 if(fullName != ''){ | ||||
|                     displayName = fullName | ||||
|                 } | ||||
|             } | ||||
|         } else if (nameFormat == ShowFullName) { | ||||
|             const fullName = Utils.getFullName(user) | ||||
|             if(fullName != ''){ | ||||
|                 displayName = fullName | ||||
|             } | ||||
|         } | ||||
|         return displayName | ||||
|     } | ||||
|  | ||||
|     static getFullName(user: IUser): string { | ||||
|         if (user.firstname != '' && user.lastname != '') { | ||||
|             return user.firstname + ' ' + user.lastname | ||||
|         } else if (user.firstname != '') { | ||||
|             return user.firstname | ||||
|         } else if (user.lastname != '') { | ||||
|             return user.lastname | ||||
|         } else { | ||||
|             return '' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static randomArray(size: number): Uint8Array { | ||||
|         const crypto = window.crypto || window.msCrypto | ||||
|         const rands = new Uint8Array(size) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user