You've already forked focalboard
mirror of
https://github.com/mattermost/focalboard.git
synced 2025-07-15 23:54:29 +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,
|
||||
|
@ -98,6 +98,9 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
||||
const me: IUser = {
|
||||
id: 'user-id-1',
|
||||
username: 'username_1',
|
||||
nickname: '',
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
props: {},
|
||||
create_at: 0,
|
||||
|
@ -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,6 +58,9 @@ const selectStyles = {
|
||||
}),
|
||||
}
|
||||
|
||||
const UserProperty = (props: Props): JSX.Element => {
|
||||
const clientConfig = useAppSelector<ClientConfig>(getClientConfig)
|
||||
|
||||
const formatOptionLabel = (user: any) => {
|
||||
let profileImg
|
||||
if (imageURLForUser) {
|
||||
@ -67,12 +75,11 @@ const formatOptionLabel = (user: any) => {
|
||||
src={profileImg}
|
||||
/>
|
||||
)}
|
||||
{user.username}
|
||||
{Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)}
|
||||
</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