Sysadmin manage user settings (#27583)
* Opened modal from system console * WIP * WIP * WIP * Handled saving user * Successfully updated user based settings * WIP * WIP * All settings are updating well * Fixed modal style * Added admin mode indicators in modal * Added confirmation dialog * Lint fixes * Added license check * Added permission check * Fixed i18n file order * type fix * Updated snapshots * Handled performance debugging setting * Some styling tweaks * Fixed text alighnment * Updated license required from professional to enterprise * Handled long user names * review fixes * Added manage setting option in user list page context menu * Added loader * Minor reordering * Removed confirm modal * Updated snapshots for removed modal * Added some tests * Lint fix * Used new selector in user detail page * Used new selector in user list page * Updated tests * Fixed an incorrect default test
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0df1a62f61
Коммит
87d983cc7f
@@ -21,7 +21,7 @@ func (api *API) InitReports() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUsersForReporting(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getUsersForReporting(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
if !(c.IsSystemAdmin()) {
|
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadUserManagementUsers) {
|
||||||
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementUsers)
|
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementUsers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func getUsersForReporting(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUserCountForReporting(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getUserCountForReporting(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
if !(c.IsSystemAdmin()) {
|
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadUserManagementUsers) {
|
||||||
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementUsers)
|
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementUsers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ func (a *App) SetDefaultProfileImage(c request.CTX, user *model.User) *model.App
|
|||||||
}
|
}
|
||||||
|
|
||||||
options := a.Config().GetSanitizeOptions()
|
options := a.Config().GetSanitizeOptions()
|
||||||
updatedUser.SanitizeProfile(options)
|
updatedUser.SanitizeProfile(options, false)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil, "")
|
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil, "")
|
||||||
message.Add("user", updatedUser)
|
message.Add("user", updatedUser)
|
||||||
@@ -1117,7 +1117,7 @@ func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
|||||||
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
|
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
|
||||||
options := a.ch.srv.userService.GetSanitizeOptions(asAdmin)
|
options := a.ch.srv.userService.GetSanitizeOptions(asAdmin)
|
||||||
|
|
||||||
user.SanitizeProfile(options)
|
user.SanitizeProfile(options, asAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateUserAsUser(c request.CTX, user *model.User, asAdmin bool) (*model.User, *model.AppError) {
|
func (a *App) UpdateUserAsUser(c request.CTX, user *model.User, asAdmin bool) (*model.User, *model.AppError) {
|
||||||
@@ -2558,7 +2558,7 @@ func (a *App) invalidateUserCacheAndPublish(rctx request.CTX, userID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options := a.Config().GetSanitizeOptions()
|
options := a.Config().GetSanitizeOptions()
|
||||||
user.SanitizeProfile(options)
|
user.SanitizeProfile(options, false)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil, "")
|
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil, "")
|
||||||
message.Add("user", user)
|
message.Add("user", user)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (us *UserService) sanitizeProfiles(users []*model.User, asAdmin bool) []*mo
|
|||||||
func (us *UserService) SanitizeProfile(user *model.User, asAdmin bool) {
|
func (us *UserService) SanitizeProfile(user *model.User, asAdmin bool) {
|
||||||
options := us.GetSanitizeOptions(asAdmin)
|
options := us.GetSanitizeOptions(asAdmin)
|
||||||
|
|
||||||
user.SanitizeProfile(options)
|
user.SanitizeProfile(options, asAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us *UserService) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
func (us *UserService) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
||||||
|
|||||||
@@ -1144,7 +1144,7 @@ func (s *SqlPostStore) prepareThreadedResponse(posts []*postWithExtra, extended,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
user.SanitizeProfile(sanitizeOptions)
|
user.SanitizeProfile(sanitizeOptions, false)
|
||||||
usersMap[user.Id] = user
|
usersMap[user.Id] = user
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (u *UserReportOptions) IsValid() *AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserReportQuery) ToReport() *UserReport {
|
func (u *UserReportQuery) ToReport() *UserReport {
|
||||||
u.ClearNonProfileFields()
|
u.ClearNonProfileFields(false)
|
||||||
return &UserReport{
|
return &UserReport{
|
||||||
User: u.User,
|
User: u.User,
|
||||||
UserPostStats: u.UserPostStats,
|
UserPostStats: u.UserPostStats,
|
||||||
|
|||||||
@@ -695,19 +695,22 @@ func (u *User) SanitizeInput(isAdmin bool) {
|
|||||||
u.Email = strings.TrimSpace(u.Email)
|
u.Email = strings.TrimSpace(u.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) ClearNonProfileFields() {
|
func (u *User) ClearNonProfileFields(asAdmin bool) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
u.AuthData = NewString("")
|
u.AuthData = NewString("")
|
||||||
u.MfaSecret = ""
|
u.MfaSecret = ""
|
||||||
u.EmailVerified = false
|
u.EmailVerified = false
|
||||||
u.AllowMarketing = false
|
u.AllowMarketing = false
|
||||||
u.NotifyProps = StringMap{}
|
|
||||||
u.LastPasswordUpdate = 0
|
u.LastPasswordUpdate = 0
|
||||||
u.FailedAttempts = 0
|
u.FailedAttempts = 0
|
||||||
|
|
||||||
|
if !asAdmin {
|
||||||
|
u.NotifyProps = StringMap{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SanitizeProfile(options map[string]bool) {
|
func (u *User) SanitizeProfile(options map[string]bool, asAdmin bool) {
|
||||||
u.ClearNonProfileFields()
|
u.ClearNonProfileFields(asAdmin)
|
||||||
|
|
||||||
u.Sanitize(options)
|
u.Sanitize(options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -551,12 +551,12 @@ func TestSanitizeProfile(t *testing.T) {
|
|||||||
Props: StringMap{UserPropsKeyRemoteEmail: "remote@doe.com"},
|
Props: StringMap{UserPropsKeyRemoteEmail: "remote@doe.com"},
|
||||||
}
|
}
|
||||||
|
|
||||||
user.SanitizeProfile(nil)
|
user.SanitizeProfile(nil, false)
|
||||||
|
|
||||||
require.Equal(t, "john@doe.com", user.Email)
|
require.Equal(t, "john@doe.com", user.Email)
|
||||||
require.Equal(t, "remote@doe.com", user.Props[UserPropsKeyRemoteEmail])
|
require.Equal(t, "remote@doe.com", user.Props[UserPropsKeyRemoteEmail])
|
||||||
|
|
||||||
user.SanitizeProfile(map[string]bool{"email": false})
|
user.SanitizeProfile(map[string]bool{"email": false}, false)
|
||||||
|
|
||||||
require.Empty(t, user.Email)
|
require.Empty(t, user.Email)
|
||||||
require.Empty(t, user.Props[UserPropsKeyRemoteEmail])
|
require.Empty(t, user.Props[UserPropsKeyRemoteEmail])
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
height: 92px;
|
height: 92px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 30px 20px 12px 30px;
|
padding: 0 20px 0 30px;
|
||||||
background-color: #295eb9;
|
background-color: #295eb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,18 +31,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.AdminUserCard__footer {
|
.AdminUserCard__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-top: solid 1px rgba(0, 0, 0, 0.2);
|
border-top: solid 1px rgba(0, 0, 0, 0.2);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdminUserCard__user-info {
|
.AdminUserCard__user-info {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdminUserCard__user-nickname {
|
.AdminUserCard__user-nickname {
|
||||||
|
|||||||
@@ -411,3 +411,630 @@ exports[`SystemUserDetail should match snapshot if MFA is enabled 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`SystemUserDetail should not show manage user settings button when user doesnt have permission 1`] = `
|
||||||
|
<div
|
||||||
|
className="SystemUserDetail wrapper--fixed"
|
||||||
|
>
|
||||||
|
<AdminHeader
|
||||||
|
withBackButton={true}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Connect(Component)
|
||||||
|
className="fa fa-angle-left back"
|
||||||
|
to="/admin_console/user_management/users"
|
||||||
|
/>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="User Configuration"
|
||||||
|
id="admin.systemUserDetail.title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AdminHeader>
|
||||||
|
<div
|
||||||
|
className="admin-console__wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="admin-console__content"
|
||||||
|
>
|
||||||
|
<AdminUserCard
|
||||||
|
body={
|
||||||
|
<React.Fragment>
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Email"
|
||||||
|
id="admin.userManagement.userDetail.email"
|
||||||
|
/>
|
||||||
|
<Memo(EmailIcon) />
|
||||||
|
<input
|
||||||
|
className="form-control"
|
||||||
|
disabled={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Username"
|
||||||
|
id="admin.userManagement.userDetail.username"
|
||||||
|
/>
|
||||||
|
<AtIcon />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Authentication Method"
|
||||||
|
id="admin.userManagement.userDetail.authenticationMethod"
|
||||||
|
/>
|
||||||
|
<Memo(ShieldOutlineIcon) />
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<React.Fragment>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Reset Password"
|
||||||
|
id="admin.user_item.resetPwd"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Activate"
|
||||||
|
id="admin.user_item.makeActive"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
isLoading={true}
|
||||||
|
/>
|
||||||
|
<AdminPanel
|
||||||
|
button={
|
||||||
|
<div
|
||||||
|
className="add-team-button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Add Team"
|
||||||
|
id="admin.userManagement.userDetail.addTeam"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
subtitle={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Teams to which this user belongs",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsSubtitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Team Membership",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsTitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="teamlistLoading"
|
||||||
|
>
|
||||||
|
<Memo(LoadingSpinner) />
|
||||||
|
</div>
|
||||||
|
</AdminPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="admin-console-save"
|
||||||
|
>
|
||||||
|
<SaveButton
|
||||||
|
btnClass=""
|
||||||
|
defaultMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Save"
|
||||||
|
id="save_button.save"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
disabled={true}
|
||||||
|
extraClasses=""
|
||||||
|
onClick={[Function]}
|
||||||
|
saving={false}
|
||||||
|
savingMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Saving"
|
||||||
|
id="save_button.saving"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="error-message"
|
||||||
|
>
|
||||||
|
<FormError
|
||||||
|
error={null}
|
||||||
|
errors={Array []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
confirmButtonClass="btn btn-danger"
|
||||||
|
confirmButtonText={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate"
|
||||||
|
id="deactivate_member_modal.deactivate"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system. Are you sure you want to deactivate {username}?"
|
||||||
|
id="deactivate_member_modal.desc"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<strong>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync."
|
||||||
|
id="deactivate_member_modal.sso_warning"
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
modalClass=""
|
||||||
|
onCancel={[Function]}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
show={false}
|
||||||
|
title={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate {username}"
|
||||||
|
id="deactivate_member_modal.title"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SystemUserDetail should show manage user settings button as activated 1`] = `
|
||||||
|
<div
|
||||||
|
className="SystemUserDetail wrapper--fixed"
|
||||||
|
>
|
||||||
|
<AdminHeader
|
||||||
|
withBackButton={true}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Connect(Component)
|
||||||
|
className="fa fa-angle-left back"
|
||||||
|
to="/admin_console/user_management/users"
|
||||||
|
/>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="User Configuration"
|
||||||
|
id="admin.systemUserDetail.title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AdminHeader>
|
||||||
|
<div
|
||||||
|
className="admin-console__wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="admin-console__content"
|
||||||
|
>
|
||||||
|
<AdminUserCard
|
||||||
|
body={
|
||||||
|
<React.Fragment>
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Email"
|
||||||
|
id="admin.userManagement.userDetail.email"
|
||||||
|
/>
|
||||||
|
<Memo(EmailIcon) />
|
||||||
|
<input
|
||||||
|
className="form-control"
|
||||||
|
disabled={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Username"
|
||||||
|
id="admin.userManagement.userDetail.username"
|
||||||
|
/>
|
||||||
|
<AtIcon />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Authentication Method"
|
||||||
|
id="admin.userManagement.userDetail.authenticationMethod"
|
||||||
|
/>
|
||||||
|
<Memo(ShieldOutlineIcon) />
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<React.Fragment>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Reset Password"
|
||||||
|
id="admin.user_item.resetPwd"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Activate"
|
||||||
|
id="admin.user_item.makeActive"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="manageUserSettingsBtn btn btn-tertiary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Manage User Settings"
|
||||||
|
id="admin.user_item.manageSettings"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
isLoading={true}
|
||||||
|
/>
|
||||||
|
<AdminPanel
|
||||||
|
button={
|
||||||
|
<div
|
||||||
|
className="add-team-button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Add Team"
|
||||||
|
id="admin.userManagement.userDetail.addTeam"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
subtitle={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Teams to which this user belongs",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsSubtitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Team Membership",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsTitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="teamlistLoading"
|
||||||
|
>
|
||||||
|
<Memo(LoadingSpinner) />
|
||||||
|
</div>
|
||||||
|
</AdminPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="admin-console-save"
|
||||||
|
>
|
||||||
|
<SaveButton
|
||||||
|
btnClass=""
|
||||||
|
defaultMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Save"
|
||||||
|
id="save_button.save"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
disabled={true}
|
||||||
|
extraClasses=""
|
||||||
|
onClick={[Function]}
|
||||||
|
saving={false}
|
||||||
|
savingMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Saving"
|
||||||
|
id="save_button.saving"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="error-message"
|
||||||
|
>
|
||||||
|
<FormError
|
||||||
|
error={null}
|
||||||
|
errors={Array []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
confirmButtonClass="btn btn-danger"
|
||||||
|
confirmButtonText={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate"
|
||||||
|
id="deactivate_member_modal.deactivate"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system. Are you sure you want to deactivate {username}?"
|
||||||
|
id="deactivate_member_modal.desc"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<strong>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync."
|
||||||
|
id="deactivate_member_modal.sso_warning"
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
modalClass=""
|
||||||
|
onCancel={[Function]}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
show={false}
|
||||||
|
title={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate {username}"
|
||||||
|
id="deactivate_member_modal.title"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SystemUserDetail should show manage user settings button as disabled when no license 1`] = `
|
||||||
|
<div
|
||||||
|
className="SystemUserDetail wrapper--fixed"
|
||||||
|
>
|
||||||
|
<AdminHeader
|
||||||
|
withBackButton={true}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Connect(Component)
|
||||||
|
className="fa fa-angle-left back"
|
||||||
|
to="/admin_console/user_management/users"
|
||||||
|
/>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="User Configuration"
|
||||||
|
id="admin.systemUserDetail.title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AdminHeader>
|
||||||
|
<div
|
||||||
|
className="admin-console__wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="admin-console__content"
|
||||||
|
>
|
||||||
|
<AdminUserCard
|
||||||
|
body={
|
||||||
|
<React.Fragment>
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Email"
|
||||||
|
id="admin.userManagement.userDetail.email"
|
||||||
|
/>
|
||||||
|
<Memo(EmailIcon) />
|
||||||
|
<input
|
||||||
|
className="form-control"
|
||||||
|
disabled={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Username"
|
||||||
|
id="admin.userManagement.userDetail.username"
|
||||||
|
/>
|
||||||
|
<AtIcon />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Authentication Method"
|
||||||
|
id="admin.userManagement.userDetail.authenticationMethod"
|
||||||
|
/>
|
||||||
|
<Memo(ShieldOutlineIcon) />
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<React.Fragment>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Reset Password"
|
||||||
|
id="admin.user_item.resetPwd"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Activate"
|
||||||
|
id="admin.user_item.makeActive"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
isLoading={true}
|
||||||
|
/>
|
||||||
|
<AdminPanel
|
||||||
|
button={
|
||||||
|
<div
|
||||||
|
className="add-team-button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Add Team"
|
||||||
|
id="admin.userManagement.userDetail.addTeam"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
subtitle={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Teams to which this user belongs",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsSubtitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Team Membership",
|
||||||
|
"id": "admin.userManagement.userDetail.teamsTitle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="teamlistLoading"
|
||||||
|
>
|
||||||
|
<Memo(LoadingSpinner) />
|
||||||
|
</div>
|
||||||
|
</AdminPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="admin-console-save"
|
||||||
|
>
|
||||||
|
<SaveButton
|
||||||
|
btnClass=""
|
||||||
|
defaultMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Save"
|
||||||
|
id="save_button.save"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
disabled={true}
|
||||||
|
extraClasses=""
|
||||||
|
onClick={[Function]}
|
||||||
|
saving={false}
|
||||||
|
savingMessage={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Saving"
|
||||||
|
id="save_button.saving"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="error-message"
|
||||||
|
>
|
||||||
|
<FormError
|
||||||
|
error={null}
|
||||||
|
errors={Array []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
confirmButtonClass="btn btn-danger"
|
||||||
|
confirmButtonText={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate"
|
||||||
|
id="deactivate_member_modal.deactivate"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system. Are you sure you want to deactivate {username}?"
|
||||||
|
id="deactivate_member_modal.desc"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<strong>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync."
|
||||||
|
id="deactivate_member_modal.sso_warning"
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
modalClass=""
|
||||||
|
onCancel={[Function]}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
show={false}
|
||||||
|
title={
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Deactivate {username}"
|
||||||
|
id="deactivate_member_modal.title"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"username": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|||||||
@@ -6,20 +6,27 @@ import {connect} from 'react-redux';
|
|||||||
|
|
||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
|
import {getUserPreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {addUserToTeam} from 'mattermost-redux/actions/teams';
|
import {addUserToTeam} from 'mattermost-redux/actions/teams';
|
||||||
import {updateUserActive, getUser, patchUser, updateUserMfa} from 'mattermost-redux/actions/users';
|
import {updateUserActive, getUser, patchUser, updateUserMfa} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
|
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
|
import {getShowLockedManageUserSettings, getShowManageUserSettings} from 'selectors/admin_console';
|
||||||
|
|
||||||
import SystemUserDetail from './system_user_detail';
|
import SystemUserDetail from './system_user_detail';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
|
|
||||||
|
const showManageUserSettings = getShowManageUserSettings(state);
|
||||||
|
const showLockedManageUserSettings = getShowLockedManageUserSettings(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mfaEnabled: config?.EnableMultifactorAuthentication === 'true' || false,
|
mfaEnabled: config?.EnableMultifactorAuthentication === 'true' || false,
|
||||||
|
showManageUserSettings,
|
||||||
|
showLockedManageUserSettings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +38,7 @@ const mapDispatchToProps = {
|
|||||||
addUserToTeam,
|
addUserToTeam,
|
||||||
setNavigationBlocked,
|
setNavigationBlocked,
|
||||||
openModal,
|
openModal,
|
||||||
|
getUserPreferences,
|
||||||
};
|
};
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
|
|||||||
@@ -37,4 +37,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.AdminUserCard__footer {
|
||||||
|
.manageUserSettingsBtn {
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import {shallowWithIntl, type MockIntl} from 'tests/helpers/intl-test-helper';
|
|||||||
|
|
||||||
describe('SystemUserDetail', () => {
|
describe('SystemUserDetail', () => {
|
||||||
const defaultProps: Props = {
|
const defaultProps: Props = {
|
||||||
|
showManageUserSettings: false,
|
||||||
|
showLockedManageUserSettings: false,
|
||||||
mfaEnabled: false,
|
mfaEnabled: false,
|
||||||
patchUser: jest.fn(),
|
patchUser: jest.fn(),
|
||||||
updateUserMfa: jest.fn(),
|
updateUserMfa: jest.fn(),
|
||||||
@@ -24,6 +26,7 @@ describe('SystemUserDetail', () => {
|
|||||||
setNavigationBlocked: jest.fn(),
|
setNavigationBlocked: jest.fn(),
|
||||||
addUserToTeam: jest.fn(),
|
addUserToTeam: jest.fn(),
|
||||||
openModal: jest.fn(),
|
openModal: jest.fn(),
|
||||||
|
getUserPreferences: jest.fn(),
|
||||||
intl: {
|
intl: {
|
||||||
formatMessage: jest.fn(),
|
formatMessage: jest.fn(),
|
||||||
} as MockIntl,
|
} as MockIntl,
|
||||||
@@ -50,6 +53,33 @@ describe('SystemUserDetail', () => {
|
|||||||
const wrapper = shallowWithIntl(<SystemUserDetail {...props}/>);
|
const wrapper = shallowWithIntl(<SystemUserDetail {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show manage user settings button as activated', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
showManageUserSettings: true,
|
||||||
|
};
|
||||||
|
const wrapper = shallowWithIntl(<SystemUserDetail {...props}/>);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show manage user settings button as disabled when no license', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
showLockedManageUserSettings: false,
|
||||||
|
};
|
||||||
|
const wrapper = shallowWithIntl(<SystemUserDetail {...props}/>);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not show manage user settings button when user doesnt have permission', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
showManageUserSettings: false,
|
||||||
|
};
|
||||||
|
const wrapper = shallowWithIntl(<SystemUserDetail {...props}/>);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getUserAuthenticationTextField', () => {
|
describe('getUserAuthenticationTextField', () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, {PureComponent} from 'react';
|
import React, {PureComponent} from 'react';
|
||||||
import type {ChangeEvent, MouseEvent} from 'react';
|
import type {ChangeEvent, MouseEvent} from 'react';
|
||||||
import type {IntlShape, WrappedComponentProps} from 'react-intl';
|
import type {IntlShape, WrappedComponentProps} from 'react-intl';
|
||||||
@@ -18,16 +19,19 @@ import AdminUserCard from 'components/admin_console/admin_user_card/admin_user_c
|
|||||||
import BlockableLink from 'components/admin_console/blockable_link';
|
import BlockableLink from 'components/admin_console/blockable_link';
|
||||||
import ResetPasswordModal from 'components/admin_console/reset_password_modal';
|
import ResetPasswordModal from 'components/admin_console/reset_password_modal';
|
||||||
import TeamList from 'components/admin_console/system_user_detail/team_list';
|
import TeamList from 'components/admin_console/system_user_detail/team_list';
|
||||||
|
import {ConfirmManageUserSettingsModal} from 'components/admin_console/system_users/system_users_list_actions/confirmManageUserSettingsModal';
|
||||||
import ConfirmModal from 'components/confirm_modal';
|
import ConfirmModal from 'components/confirm_modal';
|
||||||
import FormError from 'components/form_error';
|
import FormError from 'components/form_error';
|
||||||
import SaveButton from 'components/save_button';
|
import SaveButton from 'components/save_button';
|
||||||
import TeamSelectorModal from 'components/team_selector_modal';
|
import TeamSelectorModal from 'components/team_selector_modal';
|
||||||
|
import UserSettingsModal from 'components/user_settings/modal';
|
||||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||||
import AtIcon from 'components/widgets/icons/at_icon';
|
import AtIcon from 'components/widgets/icons/at_icon';
|
||||||
import EmailIcon from 'components/widgets/icons/email_icon';
|
import EmailIcon from 'components/widgets/icons/email_icon';
|
||||||
import SheidOutlineIcon from 'components/widgets/icons/shield_outline_icon';
|
import SheidOutlineIcon from 'components/widgets/icons/shield_outline_icon';
|
||||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||||
|
import WithTooltip from 'components/with_tooltip';
|
||||||
|
|
||||||
import {Constants, ModalIdentifiers} from 'utils/constants';
|
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||||
import {toTitleCase} from 'utils/utils';
|
import {toTitleCase} from 'utils/utils';
|
||||||
@@ -284,6 +288,37 @@ export class SystemUserDetail extends PureComponent<Props, State> {
|
|||||||
this.setState({showTeamSelectorModal: false});
|
this.setState({showTeamSelectorModal: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
openConfirmEditUserSettingsModal = () => {
|
||||||
|
if (!this.state.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.openModal({
|
||||||
|
modalId: ModalIdentifiers.CONFIRM_MANAGE_USER_SETTINGS_MODAL,
|
||||||
|
dialogType: ConfirmManageUserSettingsModal,
|
||||||
|
dialogProps: {
|
||||||
|
user: this.state.user,
|
||||||
|
onConfirm: this.openUserSettingsModal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
openUserSettingsModal = async () => {
|
||||||
|
if (!this.state.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.openModal({
|
||||||
|
modalId: ModalIdentifiers.USER_SETTINGS,
|
||||||
|
dialogType: UserSettingsModal,
|
||||||
|
dialogProps: {
|
||||||
|
adminMode: true,
|
||||||
|
isContentProductSettings: true,
|
||||||
|
userID: this.state.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='SystemUserDetail wrapper--fixed'>
|
<div className='SystemUserDetail wrapper--fixed'>
|
||||||
@@ -385,14 +420,61 @@ export class SystemUserDetail extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.props.showManageUserSettings &&
|
||||||
|
<button
|
||||||
|
className='manageUserSettingsBtn btn btn-tertiary'
|
||||||
|
onClick={this.openConfirmEditUserSettingsModal}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.user_item.manageSettings'
|
||||||
|
defaultMessage='Manage User Settings'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.props.showLockedManageUserSettings &&
|
||||||
|
<WithTooltip
|
||||||
|
id='adminUserSettingUpdateDisabled'
|
||||||
|
title={defineMessage({
|
||||||
|
id: 'generic.enterprise_feature',
|
||||||
|
defaultMessage: 'Enterprise feature',
|
||||||
|
})}
|
||||||
|
hint={defineMessage({
|
||||||
|
id: 'admin.user_item.manageSettings.disabled_tooltip',
|
||||||
|
defaultMessage: 'Please upgrade to Enterprise to manage user settings',
|
||||||
|
})}
|
||||||
|
placement='top'
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className='manageUserSettingsBtn btn disabled'
|
||||||
|
>
|
||||||
|
<div className='RestrictedIndicator__content'>
|
||||||
|
<i className={classNames('RestrictedIndicator__icon-tooltip', 'icon', 'icon-key-variant')}/>
|
||||||
|
</div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.user_item.manageSettings'
|
||||||
|
defaultMessage='Manage User Settings'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</WithTooltip>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* User's team details */}
|
{/* User's team details */}
|
||||||
<AdminPanel
|
<AdminPanel
|
||||||
title={defineMessage({id: 'admin.userManagement.userDetail.teamsTitle', defaultMessage: 'Team Membership'})}
|
title={defineMessage({
|
||||||
subtitle={defineMessage({id: 'admin.userManagement.userDetail.teamsSubtitle', defaultMessage: 'Teams to which this user belongs'})}
|
id: 'admin.userManagement.userDetail.teamsTitle',
|
||||||
|
defaultMessage: 'Team Membership',
|
||||||
|
})}
|
||||||
|
subtitle={defineMessage({
|
||||||
|
id: 'admin.userManagement.userDetail.teamsSubtitle',
|
||||||
|
defaultMessage: 'Teams to which this user belongs',
|
||||||
|
})}
|
||||||
button={
|
button={
|
||||||
<div className='add-team-button'>
|
<div className='add-team-button'>
|
||||||
<button
|
<button
|
||||||
@@ -479,6 +561,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
|
|||||||
onConfirm={this.handleDeactivateMember}
|
onConfirm={this.handleDeactivateMember}
|
||||||
onCancel={this.toggleCloseModalDeactivateMember}
|
onCancel={this.toggleCloseModalDeactivateMember}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.state.showTeamSelectorModal && (
|
{this.state.showTeamSelectorModal && (
|
||||||
<TeamSelectorModal
|
<TeamSelectorModal
|
||||||
onModalDismissed={this.toggleCloseTeamSelectorModal}
|
onModalDismissed={this.toggleCloseTeamSelectorModal}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import ConfirmModalRedux from 'components/confirm_modal_redux';
|
||||||
|
|
||||||
|
import {getDisplayName} from 'utils/utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user: UserProfile;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onExited: () => void;
|
||||||
|
onHide: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmManageUserSettingsModal(props: Props) {
|
||||||
|
const title = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='userSettings.adminMode.modal_header'
|
||||||
|
defaultMessage="Manage {userDisplayName}'s Settings"
|
||||||
|
values={{userDisplayName: getDisplayName(props.user)}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const message = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.user_item.manageSettings.confirm_dialog.body'
|
||||||
|
defaultMessage="You are about to access {userDisplayName}'s account settings. Any modifications you make will take effect immediately in their account. {userDisplayName} retains the ability to view and modify these settings at any time.<br></br><br></br> Are you sure you want to proceed with managing {userDisplayName}'s settings?"
|
||||||
|
values={{
|
||||||
|
userDisplayName: getDisplayName(props.user),
|
||||||
|
br: (x: React.ReactNode) => (<><br/>{x}</>),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmButtonText = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.user_item.manageSettings'
|
||||||
|
defaultMessage='Manage User Settings'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmModalRedux
|
||||||
|
title={title}
|
||||||
|
message={message}
|
||||||
|
confirmButtonText={confirmButtonText}
|
||||||
|
onConfirm={props.onConfirm}
|
||||||
|
onExited={props.onExited}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React, {useCallback} from 'react';
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
@@ -18,14 +18,19 @@ import {isSystemAdmin, isGuest} from 'mattermost-redux/utils/user_utils';
|
|||||||
|
|
||||||
import {adminResetMfa} from 'actions/admin_actions';
|
import {adminResetMfa} from 'actions/admin_actions';
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
|
import {getShowManageUserSettings} from 'selectors/admin_console';
|
||||||
|
|
||||||
import ManageRolesModal from 'components/admin_console/manage_roles_modal';
|
import ManageRolesModal from 'components/admin_console/manage_roles_modal';
|
||||||
import ManageTeamsModal from 'components/admin_console/manage_teams_modal';
|
import ManageTeamsModal from 'components/admin_console/manage_teams_modal';
|
||||||
import ManageTokensModal from 'components/admin_console/manage_tokens_modal';
|
import ManageTokensModal from 'components/admin_console/manage_tokens_modal';
|
||||||
import ResetEmailModal from 'components/admin_console/reset_email_modal';
|
import ResetEmailModal from 'components/admin_console/reset_email_modal';
|
||||||
import ResetPasswordModal from 'components/admin_console/reset_password_modal';
|
import ResetPasswordModal from 'components/admin_console/reset_password_modal';
|
||||||
|
import {
|
||||||
|
ConfirmManageUserSettingsModal,
|
||||||
|
} from 'components/admin_console/system_users/system_users_list_actions/confirmManageUserSettingsModal';
|
||||||
import * as Menu from 'components/menu';
|
import * as Menu from 'components/menu';
|
||||||
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
|
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
|
||||||
|
import UserSettingsModal from 'components/user_settings/modal';
|
||||||
|
|
||||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
@@ -49,6 +54,7 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const config = useSelector(getConfig);
|
const config = useSelector(getConfig);
|
||||||
const isLicensed = useSelector(getLicense)?.IsLicensed === 'true';
|
const isLicensed = useSelector(getLicense)?.IsLicensed === 'true';
|
||||||
|
const showManageUserSettings = useSelector(getShowManageUserSettings);
|
||||||
|
|
||||||
function getTranslatedUserRole(userRoles: UserProfile['roles']) {
|
function getTranslatedUserRole(userRoles: UserProfile['roles']) {
|
||||||
if (user.delete_at > 0) {
|
if (user.delete_at > 0) {
|
||||||
@@ -96,6 +102,18 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
|||||||
const onPromoteToMember = () => updateUser({roles: user.roles.replace(General.SYSTEM_GUEST_ROLE, '')});
|
const onPromoteToMember = () => updateUser({roles: user.roles.replace(General.SYSTEM_GUEST_ROLE, '')});
|
||||||
const onDemoteToGuest = () => updateUser({roles: `${user.roles} ${General.SYSTEM_GUEST_ROLE}`});
|
const onDemoteToGuest = () => updateUser({roles: `${user.roles} ${General.SYSTEM_GUEST_ROLE}`});
|
||||||
|
|
||||||
|
const openUserSettingsModal = useCallback(() => {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalId: ModalIdentifiers.USER_SETTINGS,
|
||||||
|
dialogType: UserSettingsModal,
|
||||||
|
dialogProps: {
|
||||||
|
adminMode: true,
|
||||||
|
isContentProductSettings: true,
|
||||||
|
userID: user.id,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, [dispatch, user.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.Container
|
<Menu.Container
|
||||||
menuButton={{
|
menuButton={{
|
||||||
@@ -209,6 +227,29 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
showManageUserSettings &&
|
||||||
|
<Menu.Item
|
||||||
|
id={`${menuItemIdPrefix}-manageTeams`}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.user_item.manageSettings'
|
||||||
|
defaultMessage='Manage User Settings'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalId: ModalIdentifiers.CONFIRM_MANAGE_USER_SETTINGS_MODAL,
|
||||||
|
dialogType: ConfirmManageUserSettingsModal,
|
||||||
|
dialogProps: {
|
||||||
|
user,
|
||||||
|
onConfirm: openUserSettingsModal,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
{config.ServiceSettings?.EnableUserAccessTokens &&
|
{config.ServiceSettings?.EnableUserAccessTokens &&
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
id={`${menuItemIdPrefix}-manageTokens`}
|
id={`${menuItemIdPrefix}-manageTokens`}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import React, {useCallback, useState} from 'react';
|
|||||||
import ConfirmModal from 'components/confirm_modal';
|
import ConfirmModal from 'components/confirm_modal';
|
||||||
|
|
||||||
type Props = Omit<React.ComponentProps<typeof ConfirmModal>, 'show'> & {
|
type Props = Omit<React.ComponentProps<typeof ConfirmModal>, 'show'> & {
|
||||||
onExited: () => void;
|
onExited?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ConfirmModalRedux(props: Props) {
|
export default function ConfirmModalRedux(props: Props) {
|
||||||
|
|||||||
@@ -8,34 +8,48 @@ import type {Dispatch} from 'redux';
|
|||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {updateUserActive, revokeAllSessionsForUser} from 'mattermost-redux/actions/users';
|
import {updateUserActive, revokeAllSessionsForUser} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {get, getUnreadScrollPositionPreference, makeGetCategory, syncedDraftsAreAllowed} from 'mattermost-redux/selectors/entities/preferences';
|
import {
|
||||||
|
get,
|
||||||
|
getFromPreferences, getUnreadScrollPositionFromPreference,
|
||||||
|
getUnreadScrollPositionPreference,
|
||||||
|
makeGetCategory, makeGetUserCategory,
|
||||||
|
syncedDraftsAreAllowed,
|
||||||
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import {Preferences} from 'utils/constants';
|
import {Preferences} from 'utils/constants';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './user_settings_advanced';
|
||||||
import AdvancedSettingsDisplay from './user_settings_advanced';
|
import AdvancedSettingsDisplay from './user_settings_advanced';
|
||||||
|
|
||||||
function makeMapStateToProps() {
|
function makeMapStateToProps(state: GlobalState, props: OwnProps) {
|
||||||
const getAdvancedSettingsCategory = makeGetCategory();
|
const getAdvancedSettingsCategory = props.adminMode ? makeGetUserCategory(props.currentUser.id) : makeGetCategory();
|
||||||
|
|
||||||
return (state: GlobalState) => {
|
return (state: GlobalState, props: OwnProps) => {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
|
|
||||||
const enablePreviewFeatures = config.EnablePreviewFeatures === 'true';
|
const enablePreviewFeatures = config.EnablePreviewFeatures === 'true';
|
||||||
const enableUserDeactivation = config.EnableUserDeactivation === 'true';
|
const enableUserDeactivation = config.EnableUserDeactivation === 'true';
|
||||||
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
||||||
|
|
||||||
|
let getPreference = (prefCategory: string, prefName: string, defaultValue: string) => get(state, prefCategory, prefName, defaultValue);
|
||||||
|
if (props.adminMode && props.userPreferences) {
|
||||||
|
// This ties the function to the current value of userPreferences for the current execution of this function
|
||||||
|
const preferences = props.userPreferences;
|
||||||
|
getPreference = (prefCategory, prefName, defaultValue) => getFromPreferences(preferences, prefCategory, prefName, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
advancedSettingsCategory: getAdvancedSettingsCategory(state, Preferences.CATEGORY_ADVANCED_SETTINGS),
|
advancedSettingsCategory: getAdvancedSettingsCategory(state, Preferences.CATEGORY_ADVANCED_SETTINGS),
|
||||||
sendOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', 'false'),
|
sendOnCtrlEnter: getPreference(Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', 'false'),
|
||||||
codeBlockOnCtrlEnter: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', 'true'),
|
codeBlockOnCtrlEnter: getPreference(Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', 'true'),
|
||||||
formatting: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true'),
|
formatting: getPreference(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', 'true'),
|
||||||
joinLeave: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', enableJoinLeaveMessage.toString()),
|
joinLeave: getPreference(Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', enableJoinLeaveMessage.toString()),
|
||||||
syncDrafts: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'sync_drafts', 'true'),
|
syncDrafts: getPreference(Preferences.CATEGORY_ADVANCED_SETTINGS, 'sync_drafts', 'true'),
|
||||||
currentUser: getCurrentUser(state),
|
currentUser: props.adminMode && props.currentUser ? props.currentUser : getCurrentUser(state),
|
||||||
unreadScrollPosition: getUnreadScrollPositionPreference(state),
|
unreadScrollPosition: props.adminMode && props.userPreferences ? getUnreadScrollPositionFromPreference(props.userPreferences) : getUnreadScrollPositionPreference(state),
|
||||||
enablePreviewFeatures,
|
enablePreviewFeatures,
|
||||||
enableUserDeactivation,
|
enableUserDeactivation,
|
||||||
syncedDraftsAreAllowed: syncedDraftsAreAllowed(state),
|
syncedDraftsAreAllowed: syncedDraftsAreAllowed(state),
|
||||||
|
|||||||
@@ -8,26 +8,37 @@ import type {Dispatch} from 'redux';
|
|||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {get as getPreference} from 'mattermost-redux/selectors/entities/preferences';
|
import {get as getPreference, getFromPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './join_leave_section';
|
||||||
import JoinLeaveSection from './join_leave_section';
|
import JoinLeaveSection from './join_leave_section';
|
||||||
|
|
||||||
export function mapStateToProps(state: GlobalState) {
|
export function mapStateToProps(state: GlobalState, props: OwnProps) {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
||||||
|
|
||||||
const joinLeave = getPreference(
|
let joinLeave: string;
|
||||||
state,
|
if (props.adminMode && props.userPreferences) {
|
||||||
Preferences.CATEGORY_ADVANCED_SETTINGS,
|
joinLeave = getFromPreferences(
|
||||||
Preferences.ADVANCED_FILTER_JOIN_LEAVE,
|
props.userPreferences,
|
||||||
enableJoinLeaveMessage.toString(),
|
Preferences.CATEGORY_ADVANCED_SETTINGS,
|
||||||
);
|
Preferences.ADVANCED_FILTER_JOIN_LEAVE,
|
||||||
|
enableJoinLeaveMessage.toString(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
joinLeave = getPreference(
|
||||||
|
state,
|
||||||
|
Preferences.CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
Preferences.ADVANCED_FILTER_JOIN_LEAVE,
|
||||||
|
enableJoinLeaveMessage.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId: getCurrentUserId(state),
|
currentUserId: props.adminMode ? props.currentUserId : getCurrentUserId(state),
|
||||||
joinLeave,
|
joinLeave,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ describe('mapStateToProps', () => {
|
|||||||
} as unknown as GlobalState;
|
} as unknown as GlobalState;
|
||||||
|
|
||||||
test('configuration default to true', () => {
|
test('configuration default to true', () => {
|
||||||
const props = mapStateToProps(initialState);
|
const props = mapStateToProps(initialState, {adminMode: false, currentUserId: ''});
|
||||||
expect(props.joinLeave).toEqual('true');
|
expect(props.joinLeave).toEqual('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ describe('mapStateToProps', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const props = mapStateToProps(testState);
|
const props = mapStateToProps(testState, {currentUserId: '', adminMode: false});
|
||||||
expect(props.joinLeave).toEqual('false');
|
expect(props.joinLeave).toEqual('false');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ describe('mapStateToProps', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const props = mapStateToProps(testState);
|
const props = mapStateToProps(testState, {adminMode: false, currentUserId: ''});
|
||||||
expect(props.joinLeave).toEqual('true');
|
expect(props.joinLeave).toEqual('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,7 +204,42 @@ describe('mapStateToProps', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const props = mapStateToProps(testState);
|
const props = mapStateToProps(testState, {adminMode: false, currentUserId: ''});
|
||||||
expect(props.joinLeave).toEqual('false');
|
expect(props.joinLeave).toEqual('false');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should read from preferences in props in admin mode', () => {
|
||||||
|
const testState = mergeObjects(initialState, {
|
||||||
|
entities: {
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
EnableJoinLeaveMessageByDefault: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userPreferences = {
|
||||||
|
[getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: {
|
||||||
|
category: Preferences.CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
name: Preferences.ADVANCED_FILTER_JOIN_LEAVE,
|
||||||
|
user_id: 'user_1',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const propsWithAdminMode = mapStateToProps(testState, {
|
||||||
|
currentUserId: 'user_1',
|
||||||
|
adminMode: true,
|
||||||
|
userPreferences,
|
||||||
|
});
|
||||||
|
expect(propsWithAdminMode.joinLeave).toEqual('true');
|
||||||
|
|
||||||
|
const propsWithoutAdminMode = mapStateToProps(testState, {
|
||||||
|
currentUserId: 'user_1',
|
||||||
|
adminMode: false,
|
||||||
|
userPreferences,
|
||||||
|
});
|
||||||
|
expect(propsWithoutAdminMode.joinLeave).toEqual('false');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React from 'react';
|
|||||||
import type {ReactNode, RefObject} from 'react';
|
import type {ReactNode, RefObject} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
@@ -16,10 +16,15 @@ import type SettingItemMinComponent from 'components/setting_item_min';
|
|||||||
import {AdvancedSections} from 'utils/constants';
|
import {AdvancedSections} from 'utils/constants';
|
||||||
import {a11yFocus} from 'utils/utils';
|
import {a11yFocus} from 'utils/utils';
|
||||||
|
|
||||||
type Props = {
|
export type OwnProps = {
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUserId: string;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
areAllSectionsInactive: boolean;
|
areAllSectionsInactive: boolean;
|
||||||
currentUserId: string;
|
|
||||||
joinLeave?: string;
|
joinLeave?: string;
|
||||||
onUpdateSection: (section?: string) => void;
|
onUpdateSection: (section?: string) => void;
|
||||||
renderOnOffLabel: (label: string) => ReactNode;
|
renderOnOffLabel: (label: string) => ReactNode;
|
||||||
|
|||||||
@@ -7,20 +7,28 @@ import type {ConnectedProps} from 'react-redux';
|
|||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import {isPerformanceDebuggingEnabled} from 'mattermost-redux/selectors/entities/general';
|
import {isPerformanceDebuggingEnabled} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
import {getBool, getBoolFromPreferences, getUserPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './performance_debugging_section';
|
||||||
import PerformanceDebuggingSection from './performance_debugging_section';
|
import PerformanceDebuggingSection from './performance_debugging_section';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState, props: OwnProps) {
|
||||||
|
let getPreference = (prefCategory: string, prefName: string) => getBool(state, prefCategory, prefName);
|
||||||
|
if (props.adminMode && props.currentUserId) {
|
||||||
|
const userPreferences = getUserPreferences(state, props.currentUserId);
|
||||||
|
getPreference = (prefCategory: string, prefName: string) => getBoolFromPreferences(userPreferences, prefCategory, prefName);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId: getCurrentUserId(state),
|
currentUserId: props.adminMode ? props.currentUserId : getCurrentUserId(state),
|
||||||
disableClientPlugins: getBool(state, Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_CLIENT_PLUGINS),
|
disableClientPlugins: getPreference(Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_CLIENT_PLUGINS),
|
||||||
disableTelemetry: getBool(state, Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_TELEMETRY),
|
disableTelemetry: getPreference(Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_TELEMETRY),
|
||||||
disableTypingMessages: getBool(state, Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_TYPING_MESSAGES),
|
disableTypingMessages: getPreference(Preferences.CATEGORY_PERFORMANCE_DEBUGGING, Preferences.NAME_DISABLE_TYPING_MESSAGES),
|
||||||
performanceDebuggingEnabled: isPerformanceDebuggingEnabled(state),
|
performanceDebuggingEnabled: isPerformanceDebuggingEnabled(state),
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ import {AdvancedSections} from 'utils/constants';
|
|||||||
|
|
||||||
import type {PropsFromRedux} from './index';
|
import type {PropsFromRedux} from './index';
|
||||||
|
|
||||||
type Props = PropsFromRedux & {
|
export type OwnProps = {
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUserId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PropsFromRedux & OwnProps & {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
areAllSectionsInactive: boolean;
|
areAllSectionsInactive: boolean;
|
||||||
onUpdateSection: (section?: string) => void;
|
onUpdateSection: (section?: string) => void;
|
||||||
@@ -111,6 +116,10 @@ function PerformanceDebuggingSectionExpanded(props: Props) {
|
|||||||
const [disableTypingMessages, setDisableTypingMessages] = useState(props.disableTypingMessages);
|
const [disableTypingMessages, setDisableTypingMessages] = useState(props.disableTypingMessages);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (!props.currentUserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const preferences = [];
|
const preferences = [];
|
||||||
|
|
||||||
if (disableClientPlugins !== props.disableClientPlugins) {
|
if (disableClientPlugins !== props.disableClientPlugins) {
|
||||||
@@ -138,7 +147,7 @@ function PerformanceDebuggingSectionExpanded(props: Props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferences.length !== 0) {
|
if (preferences.length !== 0 && props.currentUserId) {
|
||||||
props.savePreferences(props.currentUserId, preferences);
|
props.savePreferences(props.currentUserId, preferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import React from 'react';
|
|||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
import {FormattedMessage, defineMessages} from 'react-intl';
|
import {FormattedMessage, defineMessages} from 'react-intl';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -40,8 +40,13 @@ type Settings = {
|
|||||||
sync_drafts: Props['syncDrafts'];
|
sync_drafts: Props['syncDrafts'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = {
|
export type OwnProps = {
|
||||||
|
adminMode?: boolean;
|
||||||
currentUser: UserProfile;
|
currentUser: UserProfile;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = OwnProps & {
|
||||||
advancedSettingsCategory: PreferenceType[];
|
advancedSettingsCategory: PreferenceType[];
|
||||||
sendOnCtrlEnter: string;
|
sendOnCtrlEnter: string;
|
||||||
codeBlockOnCtrlEnter: string;
|
codeBlockOnCtrlEnter: string;
|
||||||
@@ -161,6 +166,10 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = async (settings: string[]): Promise<void> => {
|
handleSubmit = async (settings: string[]): Promise<void> => {
|
||||||
|
if (!this.props.currentUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const preferences: PreferenceType[] = [];
|
const preferences: PreferenceType[] = [];
|
||||||
const {actions, currentUser} = this.props;
|
const {actions, currentUser} = this.props;
|
||||||
const userId = currentUser.id;
|
const userId = currentUser.id;
|
||||||
@@ -796,7 +805,7 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
let makeConfirmationModal: ReactNode = '';
|
let makeConfirmationModal: ReactNode = '';
|
||||||
const currentUser = this.props.currentUser;
|
const currentUser = this.props.currentUser;
|
||||||
|
|
||||||
if (currentUser.auth_service === '' && this.props.enableUserDeactivation) {
|
if (currentUser.auth_service === '' && this.props.enableUserDeactivation && !this.props.adminMode) {
|
||||||
const active = this.props.activeSection === 'deactivateAccount';
|
const active = this.props.activeSection === 'deactivateAccount';
|
||||||
let max = null;
|
let max = null;
|
||||||
if (active) {
|
if (active) {
|
||||||
@@ -928,6 +937,9 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
areAllSectionsInactive={this.props.activeSection === ''}
|
areAllSectionsInactive={this.props.activeSection === ''}
|
||||||
onUpdateSection={this.handleUpdateSection}
|
onUpdateSection={this.handleUpdateSection}
|
||||||
renderOnOffLabel={this.renderOnOffLabel}
|
renderOnOffLabel={this.renderOnOffLabel}
|
||||||
|
adminMode={this.props.adminMode}
|
||||||
|
userPreferences={this.props.userPreferences}
|
||||||
|
currentUserId={this.props.currentUser.id}
|
||||||
/>
|
/>
|
||||||
{previewFeaturesSectionDivider}
|
{previewFeaturesSectionDivider}
|
||||||
{previewFeaturesSection}
|
{previewFeaturesSection}
|
||||||
@@ -935,6 +947,8 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
active={this.props.activeSection === AdvancedSections.PERFORMANCE_DEBUGGING}
|
active={this.props.activeSection === AdvancedSections.PERFORMANCE_DEBUGGING}
|
||||||
onUpdateSection={this.handleUpdateSection}
|
onUpdateSection={this.handleUpdateSection}
|
||||||
areAllSectionsInactive={this.props.activeSection === ''}
|
areAllSectionsInactive={this.props.activeSection === ''}
|
||||||
|
adminMode={this.props.adminMode}
|
||||||
|
currentUserId={this.props.currentUser.id}
|
||||||
/>
|
/>
|
||||||
{unreadScrollPositionSectionDivider}
|
{unreadScrollPositionSectionDivider}
|
||||||
{unreadScrollPositionSection}
|
{unreadScrollPositionSection}
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -671,6 +672,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -1023,6 +1025,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -1375,6 +1378,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -1654,6 +1658,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -1915,6 +1920,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -2267,6 +2273,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -2637,6 +2644,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -2916,6 +2924,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -3195,6 +3204,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -3474,6 +3484,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -3766,6 +3777,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -4045,6 +4057,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
@@ -4300,6 +4313,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should not show la
|
|||||||
describe="English (US)"
|
describe="English (US)"
|
||||||
max={
|
max={
|
||||||
<Memo(Connect(injectIntl(ManageLanguage)))
|
<Memo(Connect(injectIntl(ManageLanguage)))
|
||||||
|
adminMode={false}
|
||||||
locale="en"
|
locale="en"
|
||||||
updateSection={[Function]}
|
updateSection={[Function]}
|
||||||
user={
|
user={
|
||||||
|
|||||||
@@ -7,14 +7,23 @@ import type {Dispatch} from 'redux';
|
|||||||
import timezones from 'timezones.json';
|
import timezones from 'timezones.json';
|
||||||
|
|
||||||
import {CollapsedThreads} from '@mattermost/types/config';
|
import {CollapsedThreads} from '@mattermost/types/config';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
||||||
import {updateMe} from 'mattermost-redux/actions/users';
|
import {patchUser, updateMe} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {get, isCollapsedThreadsAllowed, getCollapsedThreadsPreference} from 'mattermost-redux/selectors/entities/preferences';
|
import {
|
||||||
import {getCurrentTimezoneFull, getCurrentTimezoneLabel} from 'mattermost-redux/selectors/entities/timezone';
|
get,
|
||||||
|
isCollapsedThreadsAllowed,
|
||||||
|
getCollapsedThreadsPreference,
|
||||||
|
getFromPreferences,
|
||||||
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
import {
|
||||||
|
generateCurrentTimezoneLabel,
|
||||||
|
getCurrentTimezoneFull,
|
||||||
|
getCurrentTimezoneLabel,
|
||||||
|
getTimezoneForUserProfile,
|
||||||
|
} from 'mattermost-redux/selectors/entities/timezone';
|
||||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
|
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
|
||||||
|
|
||||||
@@ -23,20 +32,17 @@ import {Preferences} from 'utils/constants';
|
|||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './user_settings_display';
|
||||||
import UserSettingsDisplay from './user_settings_display';
|
import UserSettingsDisplay from './user_settings_display';
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
user: UserProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeMapStateToProps() {
|
export function makeMapStateToProps() {
|
||||||
return (state: GlobalState, props: OwnProps) => {
|
return (state: GlobalState, props: OwnProps) => {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
const currentUserId = getCurrentUserId(state);
|
const currentUserId = getCurrentUserId(state);
|
||||||
const userTimezone = getCurrentTimezoneFull(state);
|
const userTimezone = props.adminMode ? getTimezoneForUserProfile(props.user) : getCurrentTimezoneFull(state);
|
||||||
const automaticTimezoneNotSet = userTimezone && userTimezone.useAutomaticTimezone && !userTimezone.automaticTimezone;
|
const automaticTimezoneNotSet = userTimezone && userTimezone.useAutomaticTimezone && !userTimezone.automaticTimezone;
|
||||||
const shouldAutoUpdateTimezone = !userTimezone || automaticTimezoneNotSet;
|
const shouldAutoUpdateTimezone = !userTimezone || automaticTimezoneNotSet;
|
||||||
const timezoneLabel = getCurrentTimezoneLabel(state);
|
const timezoneLabel = props.adminMode ? generateCurrentTimezoneLabel(getUserCurrentTimezone(userTimezone)) : getCurrentTimezoneLabel(state);
|
||||||
const allowCustomThemes = config.AllowCustomThemes === 'true';
|
const allowCustomThemes = config.AllowCustomThemes === 'true';
|
||||||
const enableLinkPreviews = config.EnableLinkPreviews === 'true';
|
const enableLinkPreviews = config.EnableLinkPreviews === 'true';
|
||||||
const enableThemeSelection = config.EnableThemeSelection === 'true';
|
const enableThemeSelection = config.EnableThemeSelection === 'true';
|
||||||
@@ -46,7 +52,8 @@ export function makeMapStateToProps() {
|
|||||||
const lastActiveTimeEnabled = config.EnableLastActiveTime === 'true';
|
const lastActiveTimeEnabled = config.EnableLastActiveTime === 'true';
|
||||||
|
|
||||||
let lastActiveDisplay = true;
|
let lastActiveDisplay = true;
|
||||||
if (getUser(state, currentUserId).props?.show_last_active === 'false') {
|
const user = props.adminMode ? props.user : getUser(state, currentUserId);
|
||||||
|
if (user.props?.show_last_active === 'false') {
|
||||||
lastActiveDisplay = false;
|
lastActiveDisplay = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +62,12 @@ export function makeMapStateToProps() {
|
|||||||
userLocale = config.DefaultClientLocale as string;
|
userLocale = config.DefaultClientLocale as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let getPreference = (prefCategory: string, prefName: string, defaultValue: string) => get(state, prefCategory, prefName, defaultValue);
|
||||||
|
if (props.adminMode && props.userPreferences) {
|
||||||
|
const preferences = props.userPreferences;
|
||||||
|
getPreference = (prefCategory: string, prefName: string, defaultValue: string) => getFromPreferences(preferences, prefCategory, prefName, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lockTeammateNameDisplay,
|
lockTeammateNameDisplay,
|
||||||
allowCustomThemes,
|
allowCustomThemes,
|
||||||
@@ -68,18 +81,18 @@ export function makeMapStateToProps() {
|
|||||||
userTimezone,
|
userTimezone,
|
||||||
shouldAutoUpdateTimezone,
|
shouldAutoUpdateTimezone,
|
||||||
currentUserTimezone: getUserCurrentTimezone(userTimezone) as string,
|
currentUserTimezone: getUserCurrentTimezone(userTimezone) as string,
|
||||||
availabilityStatusOnPosts: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.AVAILABILITY_STATUS_ON_POSTS, Preferences.AVAILABILITY_STATUS_ON_POSTS_DEFAULT),
|
availabilityStatusOnPosts: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.AVAILABILITY_STATUS_ON_POSTS, Preferences.AVAILABILITY_STATUS_ON_POSTS_DEFAULT),
|
||||||
militaryTime: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, Preferences.USE_MILITARY_TIME_DEFAULT),
|
militaryTime: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, Preferences.USE_MILITARY_TIME_DEFAULT),
|
||||||
teammateNameDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT, configTeammateNameDisplay),
|
teammateNameDisplay: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT, configTeammateNameDisplay),
|
||||||
channelDisplayMode: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT),
|
channelDisplayMode: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT),
|
||||||
messageDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT),
|
messageDisplay: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT),
|
||||||
colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT),
|
colorizeUsernames: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT),
|
||||||
collapseDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, Preferences.COLLAPSE_DISPLAY_DEFAULT),
|
collapseDisplay: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, Preferences.COLLAPSE_DISPLAY_DEFAULT),
|
||||||
collapsedReplyThreadsAllowUserPreference: isCollapsedThreadsAllowed(state) && getConfig(state).CollapsedThreads !== CollapsedThreads.ALWAYS_ON,
|
collapsedReplyThreadsAllowUserPreference: isCollapsedThreadsAllowed(state) && getConfig(state).CollapsedThreads !== CollapsedThreads.ALWAYS_ON,
|
||||||
collapsedReplyThreads: getCollapsedThreadsPreference(state),
|
collapsedReplyThreads: getCollapsedThreadsPreference(state),
|
||||||
clickToReply: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CLICK_TO_REPLY, Preferences.CLICK_TO_REPLY_DEFAULT),
|
clickToReply: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CLICK_TO_REPLY, Preferences.CLICK_TO_REPLY_DEFAULT),
|
||||||
linkPreviewDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, Preferences.LINK_PREVIEW_DISPLAY_DEFAULT),
|
linkPreviewDisplay: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, Preferences.LINK_PREVIEW_DISPLAY_DEFAULT),
|
||||||
oneClickReactionsOnPosts: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.ONE_CLICK_REACTIONS_ENABLED, Preferences.ONE_CLICK_REACTIONS_ENABLED_DEFAULT),
|
oneClickReactionsOnPosts: getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.ONE_CLICK_REACTIONS_ENABLED, Preferences.ONE_CLICK_REACTIONS_ENABLED_DEFAULT),
|
||||||
emojiPickerEnabled,
|
emojiPickerEnabled,
|
||||||
lastActiveDisplay,
|
lastActiveDisplay,
|
||||||
lastActiveTimeEnabled,
|
lastActiveTimeEnabled,
|
||||||
@@ -93,6 +106,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
|||||||
autoUpdateTimezone,
|
autoUpdateTimezone,
|
||||||
savePreferences,
|
savePreferences,
|
||||||
updateMe,
|
updateMe,
|
||||||
|
patchUser,
|
||||||
}, dispatch),
|
}, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
|||||||
import {bindActionCreators} from 'redux';
|
import {bindActionCreators} from 'redux';
|
||||||
import type {Dispatch} from 'redux';
|
import type {Dispatch} from 'redux';
|
||||||
|
|
||||||
import {updateMe} from 'mattermost-redux/actions/users';
|
import {patchUser, updateMe} from 'mattermost-redux/actions/users';
|
||||||
|
|
||||||
import {getLanguages} from 'i18n/i18n';
|
import {getLanguages} from 'i18n/i18n';
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
|||||||
return {
|
return {
|
||||||
actions: bindActionCreators({
|
actions: bindActionCreators({
|
||||||
updateMe,
|
updateMe,
|
||||||
|
patchUser,
|
||||||
}, dispatch),
|
}, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ describe('components/user_settings/display/manage_languages/manage_languages', (
|
|||||||
updateSection: jest.fn(),
|
updateSection: jest.fn(),
|
||||||
actions: {
|
actions: {
|
||||||
updateMe: jest.fn(() => Promise.resolve({})),
|
updateMe: jest.fn(() => Promise.resolve({})),
|
||||||
|
patchUser: jest.fn(() => Promise.resolve({})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {isKeyPressed} from 'utils/keyboard';
|
|||||||
|
|
||||||
type Actions = {
|
type Actions = {
|
||||||
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
||||||
|
patchUser: (user: UserProfile) => Promise<ActionResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -29,6 +30,7 @@ type Props = {
|
|||||||
locales: Record<string, Language>;
|
locales: Record<string, Language>;
|
||||||
updateSection: (section: string) => void;
|
updateSection: (section: string) => void;
|
||||||
actions: Actions;
|
actions: Actions;
|
||||||
|
adminMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SelectedOption = {
|
type SelectedOption = {
|
||||||
@@ -122,9 +124,10 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
|
|||||||
submitUser = (user: UserProfile) => {
|
submitUser = (user: UserProfile) => {
|
||||||
this.setState({isSaving: true});
|
this.setState({isSaving: true});
|
||||||
|
|
||||||
this.props.actions.updateMe(user).then((res) => {
|
const action = this.props.adminMode ? this.props.actions.patchUser : this.props.actions.updateMe;
|
||||||
|
action(user).then((res) => {
|
||||||
if ('data' in res) {
|
if ('data' in res) {
|
||||||
// Do nothing since changing the locale essentially refreshes the page
|
this.setState({isSaving: false});
|
||||||
} else if ('error' in res) {
|
} else if ('error' in res) {
|
||||||
let serverError;
|
let serverError;
|
||||||
const {error} = res;
|
const {error} = res;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import timezones from 'timezones.json';
|
|||||||
|
|
||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
import {updateMe} from 'mattermost-redux/actions/users';
|
import {patchUser, updateMe} from 'mattermost-redux/actions/users';
|
||||||
import {getCurrentTimezoneLabel} from 'mattermost-redux/selectors/entities/timezone';
|
import {getCurrentTimezoneLabel} from 'mattermost-redux/selectors/entities/timezone';
|
||||||
|
|
||||||
import ManageTimezones from './manage_timezones';
|
import ManageTimezones from './manage_timezones';
|
||||||
@@ -17,6 +17,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
|||||||
return {
|
return {
|
||||||
actions: bindActionCreators({
|
actions: bindActionCreators({
|
||||||
updateMe,
|
updateMe,
|
||||||
|
patchUser,
|
||||||
}, dispatch),
|
}, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ describe('components/user_settings/display/manage_timezones/manage_timezones', (
|
|||||||
updateSection: jest.fn(),
|
updateSection: jest.fn(),
|
||||||
actions: {
|
actions: {
|
||||||
updateMe: jest.fn(() => Promise.resolve({})),
|
updateMe: jest.fn(() => Promise.resolve({})),
|
||||||
|
patchUser: jest.fn(() => Promise.resolve({})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {getBrowserTimezone} from 'utils/timezone';
|
|||||||
|
|
||||||
type Actions = {
|
type Actions = {
|
||||||
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
||||||
|
patchUser: (user: UserProfile) => Promise<ActionResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -29,6 +30,7 @@ type Props = {
|
|||||||
timezones: Timezone[];
|
timezones: Timezone[];
|
||||||
timezoneLabel: string;
|
timezoneLabel: string;
|
||||||
actions: Actions;
|
actions: Actions;
|
||||||
|
adminMode?: boolean;
|
||||||
}
|
}
|
||||||
type SelectedOption = {
|
type SelectedOption = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -97,7 +99,7 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
submitUser = () => {
|
submitUser = () => {
|
||||||
const {user, actions} = this.props;
|
const {user} = this.props;
|
||||||
const {useAutomaticTimezone, automaticTimezone, manualTimezone} = this.state;
|
const {useAutomaticTimezone, automaticTimezone, manualTimezone} = this.state;
|
||||||
|
|
||||||
const timezone = {
|
const timezone = {
|
||||||
@@ -111,7 +113,8 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
|
|||||||
timezone,
|
timezone,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions.updateMe(updatedUser).
|
const action = this.props.adminMode ? this.props.actions.patchUser : this.props.actions.updateMe;
|
||||||
|
action(updatedUser).
|
||||||
then((res) => {
|
then((res) => {
|
||||||
if ('data' in res) {
|
if ('data' in res) {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const requiredProps = {
|
const requiredProps = {
|
||||||
|
adminMode: false,
|
||||||
user: user as UserProfile,
|
user: user as UserProfile,
|
||||||
updateSection: jest.fn(),
|
updateSection: jest.fn(),
|
||||||
activeSection: '',
|
activeSection: '',
|
||||||
@@ -72,6 +73,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
|||||||
autoUpdateTimezone: jest.fn(),
|
autoUpdateTimezone: jest.fn(),
|
||||||
savePreferences: jest.fn(),
|
savePreferences: jest.fn(),
|
||||||
updateMe: jest.fn(),
|
updateMe: jest.fn(),
|
||||||
|
patchUser: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|
||||||
configTeammateNameDisplay: '',
|
configTeammateNameDisplay: '',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {MessageDescriptor} from 'react-intl';
|
|||||||
import {FormattedMessage, defineMessage} from 'react-intl';
|
import {FormattedMessage, defineMessage} from 'react-intl';
|
||||||
import type {Timezone} from 'timezones.json';
|
import type {Timezone} from 'timezones.json';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
import type {UserProfile, UserTimezone} from '@mattermost/types/users';
|
import type {UserProfile, UserTimezone} from '@mattermost/types/users';
|
||||||
|
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -20,8 +20,8 @@ import SettingItem from 'components/setting_item';
|
|||||||
import SettingItemMax from 'components/setting_item_max';
|
import SettingItemMax from 'components/setting_item_max';
|
||||||
import ThemeSetting from 'components/user_settings/display/user_settings_theme';
|
import ThemeSetting from 'components/user_settings/display/user_settings_theme';
|
||||||
|
|
||||||
import type {Language} from 'i18n/i18n';
|
|
||||||
import {getLanguageInfo} from 'i18n/i18n';
|
import {getLanguageInfo} from 'i18n/i18n';
|
||||||
|
import type {Language} from 'i18n/i18n';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {getBrowserTimezone} from 'utils/timezone';
|
import {getBrowserTimezone} from 'utils/timezone';
|
||||||
import {a11yFocus} from 'utils/utils';
|
import {a11yFocus} from 'utils/utils';
|
||||||
@@ -81,7 +81,13 @@ type SectionProps ={
|
|||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
export type OwnProps = {
|
||||||
|
user: UserProfile;
|
||||||
|
adminMode?: boolean;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & {
|
||||||
user: UserProfile;
|
user: UserProfile;
|
||||||
updateSection: (section: string) => void;
|
updateSection: (section: string) => void;
|
||||||
activeSection?: string;
|
activeSection?: string;
|
||||||
@@ -120,6 +126,7 @@ type Props = {
|
|||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => void;
|
savePreferences: (userId: string, preferences: PreferenceType[]) => void;
|
||||||
autoUpdateTimezone: (deviceTimezone: string) => void;
|
autoUpdateTimezone: (deviceTimezone: string) => void;
|
||||||
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
updateMe: (user: UserProfile) => Promise<ActionResult>;
|
||||||
|
patchUser: (user: UserProfile) => Promise<ActionResult>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +215,8 @@ export default class UserSettingsDisplay extends React.PureComponent<Props, Stat
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
actions.updateMe(updatedUser).
|
const action = this.props.adminMode ? actions.patchUser : actions.updateMe;
|
||||||
|
action(updatedUser).
|
||||||
then((res) => {
|
then((res) => {
|
||||||
if ('data' in res) {
|
if ('data' in res) {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
@@ -873,6 +881,7 @@ export default class UserSettingsDisplay extends React.PureComponent<Props, Stat
|
|||||||
automaticTimezone={userTimezone.automaticTimezone}
|
automaticTimezone={userTimezone.automaticTimezone}
|
||||||
manualTimezone={userTimezone.manualTimezone}
|
manualTimezone={userTimezone.manualTimezone}
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
|
adminMode={this.props.adminMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1076,6 +1085,7 @@ export default class UserSettingsDisplay extends React.PureComponent<Props, Stat
|
|||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
locale={userLocale}
|
locale={userLocale}
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
|
adminMode={this.props.adminMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -1088,7 +1098,7 @@ export default class UserSettingsDisplay extends React.PureComponent<Props, Stat
|
|||||||
}
|
}
|
||||||
|
|
||||||
let themeSection;
|
let themeSection;
|
||||||
if (this.props.enableThemeSelection) {
|
if (this.props.enableThemeSelection && !this.props.adminMode) {
|
||||||
themeSection = (
|
themeSection = (
|
||||||
<div>
|
<div>
|
||||||
<ThemeSetting
|
<ThemeSetting
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.userSettingDesktopHeaderInfo {
|
.userSettingDesktopHeaderInfo {
|
||||||
|
margin-top: -20px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
.btn {
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import type {PreferencesType} from '@mattermost/types/preferences';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import type {PluginConfiguration} from 'types/plugins/user_settings';
|
import type {PluginConfiguration} from 'types/plugins/user_settings';
|
||||||
@@ -26,6 +27,8 @@ export type Props = {
|
|||||||
setEnforceFocus: () => void;
|
setEnforceFocus: () => void;
|
||||||
setRequireConfirm: () => void;
|
setRequireConfirm: () => void;
|
||||||
pluginSettings: {[tabName: string]: PluginConfiguration};
|
pluginSettings: {[tabName: string]: PluginConfiguration};
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
adminMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UserSettings(props: Props) {
|
export default function UserSettings(props: Props) {
|
||||||
@@ -64,6 +67,8 @@ export default function UserSettings(props: Props) {
|
|||||||
updateSection={props.updateSection}
|
updateSection={props.updateSection}
|
||||||
closeModal={props.closeModal}
|
closeModal={props.closeModal}
|
||||||
collapseModal={props.collapseModal}
|
collapseModal={props.collapseModal}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -78,6 +83,8 @@ export default function UserSettings(props: Props) {
|
|||||||
collapseModal={props.collapseModal}
|
collapseModal={props.collapseModal}
|
||||||
setEnforceFocus={props.setEnforceFocus}
|
setEnforceFocus={props.setEnforceFocus}
|
||||||
setRequireConfirm={props.setRequireConfirm}
|
setRequireConfirm={props.setRequireConfirm}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -89,6 +96,9 @@ export default function UserSettings(props: Props) {
|
|||||||
updateSection={props.updateSection}
|
updateSection={props.updateSection}
|
||||||
closeModal={props.closeModal}
|
closeModal={props.closeModal}
|
||||||
collapseModal={props.collapseModal}
|
collapseModal={props.collapseModal}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
currentUserId={props.user.id}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -100,6 +110,9 @@ export default function UserSettings(props: Props) {
|
|||||||
updateSection={props.updateSection}
|
updateSection={props.updateSection}
|
||||||
closeModal={props.closeModal}
|
closeModal={props.closeModal}
|
||||||
collapseModal={props.collapseModal}
|
collapseModal={props.collapseModal}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
currentUser={props.user}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import {connect} from 'react-redux';
|
|||||||
import {bindActionCreators} from 'redux';
|
import {bindActionCreators} from 'redux';
|
||||||
import type {Dispatch} from 'redux';
|
import type {Dispatch} from 'redux';
|
||||||
|
|
||||||
import {sendVerificationEmail} from 'mattermost-redux/actions/users';
|
import {getUserPreferences} from 'mattermost-redux/actions/preferences';
|
||||||
|
import {getUser, sendVerificationEmail} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
import {getUserPreferences as getUserPreferencesSelector} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
import {getCurrentUser, getUser as getUserSelector} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import {getPluginUserSettings} from 'selectors/plugins';
|
import {getPluginUserSettings} from 'selectors/plugins';
|
||||||
|
|
||||||
@@ -18,14 +20,19 @@ import type {GlobalState} from 'types/store';
|
|||||||
|
|
||||||
const UserSettingsModalAsync = makeAsyncComponent('UserSettingsModal', lazy(() => import('./user_settings_modal')));
|
const UserSettingsModalAsync = makeAsyncComponent('UserSettingsModal', lazy(() => import('./user_settings_modal')));
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
import type {OwnProps} from './user_settings_modal';
|
||||||
|
|
||||||
|
function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
|
|
||||||
const sendEmailNotifications = config.SendEmailNotifications === 'true';
|
const sendEmailNotifications = config.SendEmailNotifications === 'true';
|
||||||
const requireEmailVerification = config.RequireEmailVerification === 'true';
|
const requireEmailVerification = config.RequireEmailVerification === 'true';
|
||||||
|
|
||||||
|
const currentUser = ownProps.adminMode && ownProps.userID ? getUserSelector(state, ownProps.userID) : getCurrentUser(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUser: getCurrentUser(state),
|
currentUser,
|
||||||
|
userPreferences: ownProps.adminMode && ownProps.userID ? getUserPreferencesSelector(state, ownProps.userID) : undefined,
|
||||||
sendEmailNotifications,
|
sendEmailNotifications,
|
||||||
requireEmailVerification,
|
requireEmailVerification,
|
||||||
pluginSettings: getPluginUserSettings(state),
|
pluginSettings: getPluginUserSettings(state),
|
||||||
@@ -36,6 +43,8 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
|||||||
return {
|
return {
|
||||||
actions: bindActionCreators({
|
actions: bindActionCreators({
|
||||||
sendVerificationEmail,
|
sendVerificationEmail,
|
||||||
|
getUserPreferences,
|
||||||
|
getUser,
|
||||||
}, dispatch),
|
}, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import {injectIntl} from 'react-intl';
|
import {FormattedMessage, injectIntl} from 'react-intl';
|
||||||
import type {IntlShape} from 'react-intl';
|
import type {IntlShape} from 'react-intl';
|
||||||
|
|
||||||
|
import type {PreferencesType} from '@mattermost/types/preferences';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -14,20 +15,31 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
|
|||||||
import ConfirmModal from 'components/confirm_modal';
|
import ConfirmModal from 'components/confirm_modal';
|
||||||
import SettingsSidebar from 'components/settings_sidebar';
|
import SettingsSidebar from 'components/settings_sidebar';
|
||||||
import UserSettings from 'components/user_settings';
|
import UserSettings from 'components/user_settings';
|
||||||
|
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||||
|
import SmartLoader from 'components/widgets/smartLoader';
|
||||||
|
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
|
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
|
||||||
import {stopTryNotificationRing} from 'utils/notification_sounds';
|
import {stopTryNotificationRing} from 'utils/notification_sounds';
|
||||||
|
import {getDisplayName} from 'utils/utils';
|
||||||
|
|
||||||
import type {PluginConfiguration} from 'types/plugins/user_settings';
|
import type {PluginConfiguration} from 'types/plugins/user_settings';
|
||||||
|
|
||||||
export type Props = {
|
export type OwnProps = {
|
||||||
currentUser: UserProfile;
|
userID?: string;
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUser?: UserProfile;
|
||||||
|
isContentProductSettings: boolean;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = OwnProps & {
|
||||||
onExited: () => void;
|
onExited: () => void;
|
||||||
intl: IntlShape;
|
intl: IntlShape;
|
||||||
isContentProductSettings: boolean;
|
|
||||||
actions: {
|
actions: {
|
||||||
sendVerificationEmail: (email: string) => Promise<ActionResult>;
|
sendVerificationEmail: (email: string) => Promise<ActionResult>;
|
||||||
|
getUserPreferences: (userID: string) => Promise<unknown>;
|
||||||
|
getUser: (userID: string) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
pluginSettings: {[pluginId: string]: PluginConfiguration};
|
pluginSettings: {[pluginId: string]: PluginConfiguration};
|
||||||
}
|
}
|
||||||
@@ -39,6 +51,7 @@ type State = {
|
|||||||
enforceFocus?: boolean;
|
enforceFocus?: boolean;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
resendStatus: string;
|
resendStatus: string;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserSettingsModal extends React.PureComponent<Props, State> {
|
class UserSettingsModal extends React.PureComponent<Props, State> {
|
||||||
@@ -57,6 +70,7 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
enforceFocus: true,
|
enforceFocus: true,
|
||||||
show: true,
|
show: true,
|
||||||
resendStatus: '',
|
resendStatus: '',
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.requireConfirm = false;
|
this.requireConfirm = false;
|
||||||
@@ -84,6 +98,22 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
|
||||||
|
if (this.props.adminMode && this.props.userID) {
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
if (!this.props.userPreferences) {
|
||||||
|
this.props.actions.getUserPreferences(this.props.userID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.props.currentUser) {
|
||||||
|
this.props.actions.getUser(this.props.userID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.props.adminMode) {
|
||||||
|
this.setState({loading: false});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -97,6 +127,10 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoadingFinished = () => {
|
||||||
|
this.setState({loading: false});
|
||||||
|
};
|
||||||
|
|
||||||
handleKeyDown = (e: KeyboardEvent) => {
|
handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.A)) {
|
if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.A)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -275,17 +309,25 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
if (this.props.currentUser == null) {
|
|
||||||
return (<div/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const modalTitle = this.props.isContentProductSettings ? formatMessage({
|
let modalTitle: string;
|
||||||
id: 'global_header.productSettings',
|
|
||||||
defaultMessage: 'Settings',
|
if (this.props.adminMode && this.props.currentUser) {
|
||||||
}) : formatMessage({
|
modalTitle = formatMessage({
|
||||||
id: 'user.settings.modal.title',
|
id: 'userSettings.adminMode.modal_header',
|
||||||
defaultMessage: 'Profile',
|
defaultMessage: "{userDisplayName}'s Settings",
|
||||||
});
|
}, {
|
||||||
|
userDisplayName: getDisplayName(this.props.currentUser),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
modalTitle = this.props.isContentProductSettings ? formatMessage({
|
||||||
|
id: 'global_header.productSettings',
|
||||||
|
defaultMessage: 'Settings',
|
||||||
|
}) : formatMessage({
|
||||||
|
id: 'user.settings.modal.title',
|
||||||
|
defaultMessage: 'Profile',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -308,37 +350,63 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
{modalTitle}
|
{modalTitle}
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
|
|
||||||
|
{
|
||||||
|
this.props.adminMode &&
|
||||||
|
<div className='adminModeBadge'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='userSettings.adminMode.admin_mode_badge'
|
||||||
|
defaultMessage='Admin Mode'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body ref={this.modalBodyRef}>
|
<Modal.Body ref={this.modalBodyRef}>
|
||||||
<div className='settings-table'>
|
{
|
||||||
<div className='settings-links'>
|
this.props.adminMode &&
|
||||||
<SettingsSidebar
|
<SmartLoader
|
||||||
tabs={this.props.isContentProductSettings ? this.getUserSettingsTabs() : this.getProfileSettingsTab()}
|
loading={this.props.adminMode && (!this.props.userPreferences || !this.props.currentUser)}
|
||||||
pluginTabs={this.props.isContentProductSettings ? this.getPluginsSettingsTab() : []}
|
className='loadingIndicator'
|
||||||
activeTab={this.state.active_tab}
|
onLoaded={this.setLoadingFinished}
|
||||||
updateTab={this.updateTab}
|
>
|
||||||
/>
|
<LoadingSpinner/>
|
||||||
</div>
|
</SmartLoader>
|
||||||
<div className='settings-content minimize-settings'>
|
}
|
||||||
<UserSettings
|
|
||||||
activeTab={this.state.active_tab}
|
{
|
||||||
activeSection={this.state.active_section}
|
!this.state.loading && this.props.currentUser &&
|
||||||
updateSection={this.updateSection}
|
<div className='settings-table'>
|
||||||
updateTab={this.updateTab}
|
<div className='settings-links'>
|
||||||
closeModal={this.closeModal}
|
<SettingsSidebar
|
||||||
collapseModal={this.collapseModal}
|
tabs={this.props.isContentProductSettings ? this.getUserSettingsTabs() : this.getProfileSettingsTab()}
|
||||||
setEnforceFocus={(enforceFocus?: boolean) => this.setState({enforceFocus})}
|
pluginTabs={this.props.isContentProductSettings ? this.getPluginsSettingsTab() : []}
|
||||||
setRequireConfirm={
|
activeTab={this.state.active_tab}
|
||||||
(requireConfirm?: boolean, customConfirmAction?: () => () => void) => {
|
updateTab={this.updateTab}
|
||||||
this.requireConfirm = requireConfirm!;
|
/>
|
||||||
this.customConfirmAction = customConfirmAction!;
|
</div>
|
||||||
|
<div className='settings-content minimize-settings'>
|
||||||
|
<UserSettings
|
||||||
|
activeTab={this.state.active_tab}
|
||||||
|
activeSection={this.state.active_section}
|
||||||
|
updateSection={this.updateSection}
|
||||||
|
updateTab={this.updateTab}
|
||||||
|
closeModal={this.closeModal}
|
||||||
|
collapseModal={this.collapseModal}
|
||||||
|
setEnforceFocus={(enforceFocus?: boolean) => this.setState({enforceFocus})}
|
||||||
|
setRequireConfirm={
|
||||||
|
(requireConfirm?: boolean, customConfirmAction?: () => () => void) => {
|
||||||
|
this.requireConfirm = requireConfirm!;
|
||||||
|
this.customConfirmAction = customConfirmAction!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
pluginSettings={this.props.pluginSettings}
|
||||||
pluginSettings={this.props.pluginSettings}
|
user={this.props.currentUser}
|
||||||
user={this.props.currentUser}
|
adminMode={this.props.adminMode}
|
||||||
/>
|
userPreferences={this.props.userPreferences}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
title={formatMessage({id: 'user.settings.modal.confirmTitle', defaultMessage: 'Discard Changes?'})}
|
title={formatMessage({id: 'user.settings.modal.confirmTitle', defaultMessage: 'Discard Changes?'})}
|
||||||
@@ -346,7 +414,10 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
|
|||||||
id: 'user.settings.modal.confirmMsg',
|
id: 'user.settings.modal.confirmMsg',
|
||||||
defaultMessage: 'You have unsaved changes, are you sure you want to discard them?',
|
defaultMessage: 'You have unsaved changes, are you sure you want to discard them?',
|
||||||
})}
|
})}
|
||||||
confirmButtonText={formatMessage({id: 'user.settings.modal.confirmBtns', defaultMessage: 'Yes, Discard'})}
|
confirmButtonText={formatMessage({
|
||||||
|
id: 'user.settings.modal.confirmBtns',
|
||||||
|
defaultMessage: 'Yes, Discard',
|
||||||
|
})}
|
||||||
show={this.state.showConfirmModal}
|
show={this.state.showConfirmModal}
|
||||||
onConfirm={this.handleConfirm}
|
onConfirm={this.handleConfirm}
|
||||||
onCancel={this.handleCancelConfirmation}
|
onCancel={this.handleCancelConfirmation}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
|
|
||||||
import {connect, type ConnectedProps} from 'react-redux';
|
import {connect, type ConnectedProps} from 'react-redux';
|
||||||
|
|
||||||
import {updateMe} from 'mattermost-redux/actions/users';
|
import {patchUser, updateMe} from 'mattermost-redux/actions/users';
|
||||||
import {getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
import {getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
import {
|
||||||
|
isCollapsedThreadsEnabled,
|
||||||
|
isCollapsedThreadsEnabledForUser,
|
||||||
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
|
||||||
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
|
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
|
||||||
|
|
||||||
@@ -14,9 +17,11 @@ import {isEnterpriseOrCloudOrSKUStarterFree} from 'utils/license_utils';
|
|||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './user_settings_notifications';
|
||||||
import UserSettingsNotifications from './user_settings_notifications';
|
import UserSettingsNotifications from './user_settings_notifications';
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => {
|
const mapStateToProps = (state: GlobalState, props: OwnProps) => {
|
||||||
|
// server config, related to server configuration, not the user
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
|
|
||||||
const sendPushNotifications = config.SendPushNotifications === 'true';
|
const sendPushNotifications = config.SendPushNotifications === 'true';
|
||||||
@@ -30,16 +35,16 @@ const mapStateToProps = (state: GlobalState) => {
|
|||||||
return {
|
return {
|
||||||
sendPushNotifications,
|
sendPushNotifications,
|
||||||
enableAutoResponder,
|
enableAutoResponder,
|
||||||
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
|
isCollapsedThreadsEnabled: props.adminMode && props.userPreferences ? isCollapsedThreadsEnabledForUser(state, props.userPreferences) : isCollapsedThreadsEnabled(state),
|
||||||
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
|
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
|
||||||
isEnterpriseOrCloudOrSKUStarterFree: isEnterpriseOrCloudOrSKUStarterFree(license, subscriptionProduct, isEnterpriseReady),
|
isEnterpriseOrCloudOrSKUStarterFree: isEnterpriseOrCloudOrSKUStarterFree(license, subscriptionProduct, isEnterpriseReady),
|
||||||
isEnterpriseReady,
|
isEnterpriseReady,
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
updateMe,
|
updateMe,
|
||||||
|
patchUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
|||||||
closeModal: jest.fn(),
|
closeModal: jest.fn(),
|
||||||
collapseModal: jest.fn(),
|
collapseModal: jest.fn(),
|
||||||
updateMe: jest.fn(() => Promise.resolve({})),
|
updateMe: jest.fn(() => Promise.resolve({})),
|
||||||
|
patchUser: jest.fn(() => Promise.resolve({})),
|
||||||
isCollapsedThreadsEnabled: true,
|
isCollapsedThreadsEnabled: true,
|
||||||
sendPushNotifications: false,
|
sendPushNotifications: false,
|
||||||
enableAutoResponder: false,
|
enableAutoResponder: false,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {Styles as ReactSelectStyles, ValueType} from 'react-select';
|
|||||||
import CreatableReactSelect from 'react-select/creatable';
|
import CreatableReactSelect from 'react-select/creatable';
|
||||||
|
|
||||||
import {LightbulbOutlineIcon} from '@mattermost/compass-icons/components';
|
import {LightbulbOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {PreferencesType} from '@mattermost/types/preferences';
|
||||||
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
|
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
@@ -40,12 +41,14 @@ type MultiInputValue = {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type OwnProps = {
|
export type OwnProps = {
|
||||||
user: UserProfile;
|
user: UserProfile;
|
||||||
updateSection: (section: string) => void;
|
updateSection: (section: string) => void;
|
||||||
activeSection: string;
|
activeSection: string;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
collapseModal: () => void;
|
collapseModal: () => void;
|
||||||
|
adminMode?: boolean;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
|
export type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
|
||||||
@@ -284,7 +287,20 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
|||||||
this.setState({isSaving: true});
|
this.setState({isSaving: true});
|
||||||
stopTryNotificationRing();
|
stopTryNotificationRing();
|
||||||
|
|
||||||
const {data: updatedUser, error} = await this.props.updateMe({notify_props: data});
|
let updatedUser: UserProfile | undefined;
|
||||||
|
let error;
|
||||||
|
|
||||||
|
if (this.props.adminMode) {
|
||||||
|
const payloadUser = {...this.props.user, notify_props: data};
|
||||||
|
const response = await this.props.patchUser(payloadUser);
|
||||||
|
updatedUser = response.data;
|
||||||
|
error = response.error;
|
||||||
|
} else {
|
||||||
|
const response = await this.props.updateMe({notify_props: data});
|
||||||
|
updatedUser = response.data;
|
||||||
|
error = response.error;
|
||||||
|
}
|
||||||
|
|
||||||
if (updatedUser) {
|
if (updatedUser) {
|
||||||
this.handleUpdateSection('');
|
this.handleUpdateSection('');
|
||||||
this.setState(getDefaultStateFromProps(this.props));
|
this.setState(getDefaultStateFromProps(this.props));
|
||||||
|
|||||||
@@ -4,17 +4,18 @@
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {getVisibleDmGmLimit} from 'mattermost-redux/selectors/entities/preferences';
|
import {getUserVisibleDmGmLimit, getVisibleDmGmLimit} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './limit_visible_gms_dms';
|
||||||
import LimitVisibleGMsDMs from './limit_visible_gms_dms';
|
import LimitVisibleGMsDMs from './limit_visible_gms_dms';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||||
return {
|
return {
|
||||||
currentUserId: getCurrentUserId(state),
|
currentUserId: ownProps.adminMode ? ownProps.currentUserId : getCurrentUserId(state),
|
||||||
dmGmLimit: getVisibleDmGmLimit(state),
|
dmGmLimit: ownProps.adminMode && ownProps.userPreferences ? getUserVisibleDmGmLimit(ownProps.userPreferences) : getVisibleDmGmLimit(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
import ReactSelect from 'react-select';
|
import ReactSelect from 'react-select';
|
||||||
import type {ValueType} from 'react-select';
|
import type {ValueType} from 'react-select';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -23,10 +23,15 @@ type Limit = {
|
|||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
export type OwnProps = {
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUserId?: string;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
areAllSectionsInactive: boolean;
|
areAllSectionsInactive: boolean;
|
||||||
currentUserId: string;
|
|
||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
||||||
dmGmLimit: number;
|
dmGmLimit: number;
|
||||||
updateSection: (section: string) => void;
|
updateSection: (section: string) => void;
|
||||||
@@ -99,6 +104,10 @@ export default class LimitVisibleGMsDMs extends React.PureComponent<Props, State
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = async () => {
|
handleSubmit = async () => {
|
||||||
|
if (!this.props.currentUserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({isSaving: true});
|
this.setState({isSaving: true});
|
||||||
|
|
||||||
await this.props.savePreferences(this.props.currentUserId, [{
|
await this.props.savePreferences(this.props.currentUserId, [{
|
||||||
|
|||||||
@@ -4,17 +4,23 @@
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {shouldShowUnreadsCategory} from 'mattermost-redux/selectors/entities/preferences';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {
|
||||||
|
calculateUserShouldShowUnreadsCategory,
|
||||||
|
shouldShowUnreadsCategory,
|
||||||
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import type {OwnProps} from './show_unreads_category';
|
||||||
import ShowUnreadsCategory from './show_unreads_category';
|
import ShowUnreadsCategory from './show_unreads_category';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState, props: OwnProps) {
|
||||||
|
const serverDefault = getConfig(state).ExperimentalGroupUnreadChannels;
|
||||||
return {
|
return {
|
||||||
currentUserId: getCurrentUserId(state),
|
currentUserId: props.adminMode ? props.currentUserId : getCurrentUserId(state),
|
||||||
showUnreadsCategory: shouldShowUnreadsCategory(state),
|
showUnreadsCategory: props.adminMode && props.userPreferences ? calculateUserShouldShowUnreadsCategory(props.userPreferences, serverDefault) : shouldShowUnreadsCategory(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React from 'react';
|
|||||||
import type {RefObject} from 'react';
|
import type {RefObject} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -16,10 +16,15 @@ import type SettingItemMinComponent from 'components/setting_item_min';
|
|||||||
|
|
||||||
import {a11yFocus} from 'utils/utils';
|
import {a11yFocus} from 'utils/utils';
|
||||||
|
|
||||||
type Props = {
|
export type OwnProps = {
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUserId?: string;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
areAllSectionsInactive: boolean;
|
areAllSectionsInactive: boolean;
|
||||||
currentUserId: string;
|
|
||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
||||||
showUnreadsCategory: boolean;
|
showUnreadsCategory: boolean;
|
||||||
updateSection: (section: string) => void;
|
updateSection: (section: string) => void;
|
||||||
@@ -75,6 +80,11 @@ export default class ShowUnreadsCategory extends React.PureComponent<Props, Stat
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = async () => {
|
handleSubmit = async () => {
|
||||||
|
if (!this.props.currentUserId) {
|
||||||
|
// Only for type safety, won't actually happen
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({isSaving: true});
|
this.setState({isSaving: true});
|
||||||
|
|
||||||
await this.props.savePreferences(this.props.currentUserId, [{
|
await this.props.savePreferences(this.props.currentUserId, [{
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import type {PreferencesType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import LimitVisibleGMsDMs from './limit_visible_gms_dms';
|
import LimitVisibleGMsDMs from './limit_visible_gms_dms';
|
||||||
import ShowUnreadsCategory from './show_unreads_category';
|
import ShowUnreadsCategory from './show_unreads_category';
|
||||||
|
|
||||||
@@ -15,6 +17,9 @@ export interface Props {
|
|||||||
activeSection: string;
|
activeSection: string;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
collapseModal: () => void;
|
collapseModal: () => void;
|
||||||
|
adminMode?: boolean;
|
||||||
|
currentUserId?: string;
|
||||||
|
userPreferences?: PreferencesType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserSettingsSidebar(props: Props): JSX.Element {
|
export default function UserSettingsSidebar(props: Props): JSX.Element {
|
||||||
@@ -48,12 +53,18 @@ export default function UserSettingsSidebar(props: Props): JSX.Element {
|
|||||||
active={props.activeSection === 'showUnreadsCategory'}
|
active={props.activeSection === 'showUnreadsCategory'}
|
||||||
updateSection={props.updateSection}
|
updateSection={props.updateSection}
|
||||||
areAllSectionsInactive={props.activeSection === ''}
|
areAllSectionsInactive={props.activeSection === ''}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
currentUserId={props.currentUserId}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
<LimitVisibleGMsDMs
|
<LimitVisibleGMsDMs
|
||||||
active={props.activeSection === 'limitVisibleGMsDMs'}
|
active={props.activeSection === 'limitVisibleGMsDMs'}
|
||||||
updateSection={props.updateSection}
|
updateSection={props.updateSection}
|
||||||
areAllSectionsInactive={props.activeSection === ''}
|
areAllSectionsInactive={props.activeSection === ''}
|
||||||
|
adminMode={props.adminMode}
|
||||||
|
currentUserId={props.currentUserId}
|
||||||
|
userPreferences={props.userPreferences}
|
||||||
/>
|
/>
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
37
webapp/channels/src/components/widgets/smartLoader/index.tsx
Обычный файл
37
webapp/channels/src/components/widgets/smartLoader/index.tsx
Обычный файл
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {type ReactNode, useEffect, useState} from 'react';
|
||||||
|
|
||||||
|
const DEFAULT_MIN_LOADER_DURATION = 1500;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
loading: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
onLoaded: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SmartLoader = ({loading, children, className, onLoaded}: Props) => {
|
||||||
|
const [timeoutFinished, setTimeoutFinished] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setTimeoutFinished(true);
|
||||||
|
}, DEFAULT_MIN_LOADER_DURATION);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && timeoutFinished) {
|
||||||
|
onLoaded();
|
||||||
|
}
|
||||||
|
}, [loading, timeoutFinished, onLoaded]);
|
||||||
|
|
||||||
|
return loading || !timeoutFinished ? (
|
||||||
|
<div className={`SmartLoader ${className}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmartLoader;
|
||||||
@@ -2702,6 +2702,9 @@
|
|||||||
"admin.user_item.makeActive": "Activate",
|
"admin.user_item.makeActive": "Activate",
|
||||||
"admin.user_item.makeMember": "Make Team Member",
|
"admin.user_item.makeMember": "Make Team Member",
|
||||||
"admin.user_item.makeTeamAdmin": "Make Team Admin",
|
"admin.user_item.makeTeamAdmin": "Make Team Admin",
|
||||||
|
"admin.user_item.manageSettings": "Manage User Settings",
|
||||||
|
"admin.user_item.manageSettings.confirm_dialog.body": "You are about to access {userDisplayName}'s account settings. Any modifications you make will take effect immediately in their account. {userDisplayName} retains the ability to view and modify these settings at any time.<br></br><br></br> Are you sure you want to proceed with managing {userDisplayName}'s settings?",
|
||||||
|
"admin.user_item.manageSettings.disabled_tooltip": "Please upgrade to Enterprise to manage user settings",
|
||||||
"admin.user_item.manageTeams": "Manage Teams",
|
"admin.user_item.manageTeams": "Manage Teams",
|
||||||
"admin.user_item.member": "Member",
|
"admin.user_item.member": "Member",
|
||||||
"admin.user_item.menuAriaLabel": "User Actions Menu",
|
"admin.user_item.menuAriaLabel": "User Actions Menu",
|
||||||
@@ -3768,6 +3771,7 @@
|
|||||||
"generic_modal.confirm": "Confirm",
|
"generic_modal.confirm": "Confirm",
|
||||||
"generic.close": "Close",
|
"generic.close": "Close",
|
||||||
"generic.done": "Done",
|
"generic.done": "Done",
|
||||||
|
"generic.enterprise_feature": "Enterprise Feature",
|
||||||
"generic.next": "Next",
|
"generic.next": "Next",
|
||||||
"generic.okay": "Okay",
|
"generic.okay": "Okay",
|
||||||
"generic.previous": "Previous",
|
"generic.previous": "Previous",
|
||||||
@@ -5713,6 +5717,8 @@
|
|||||||
"userGuideHelp.trainingResources": "Training resources",
|
"userGuideHelp.trainingResources": "Training resources",
|
||||||
"users_limits_announcement_bar.copyText": "User limits exceeded. Contact administrator with: {ErrorCode}",
|
"users_limits_announcement_bar.copyText": "User limits exceeded. Contact administrator with: {ErrorCode}",
|
||||||
"users_limits_announcement_bar.ctaText": "Learn More",
|
"users_limits_announcement_bar.ctaText": "Learn More",
|
||||||
|
"userSettings.adminMode.admin_mode_badge": "Admin Mode",
|
||||||
|
"userSettings.adminMode.modal_header": "Manage {userDisplayName}'s Settings",
|
||||||
"userSettingsModal.pluginPreferences.header": "PLUGIN PREFERENCES",
|
"userSettingsModal.pluginPreferences.header": "PLUGIN PREFERENCES",
|
||||||
"version_bar.new": "A new version of Mattermost is available.",
|
"version_bar.new": "A new version of Mattermost is available.",
|
||||||
"version_bar.refresh": "Refresh the app now",
|
"version_bar.refresh": "Refresh the app now",
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ export default keyMirror({
|
|||||||
RECEIVED_PREFERENCES: null,
|
RECEIVED_PREFERENCES: null,
|
||||||
RECEIVED_ALL_PREFERENCES: null,
|
RECEIVED_ALL_PREFERENCES: null,
|
||||||
DELETED_PREFERENCES: null,
|
DELETED_PREFERENCES: null,
|
||||||
|
RECEIVED_USER_PREFERENCES: null,
|
||||||
|
RECEIVED_USER_ALL_PREFERENCES: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ export function getMyPreferences() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for fetching some other user's preferences other than current user
|
||||||
|
export function getUserPreferences(userID: string) {
|
||||||
|
return bindClientFunc({
|
||||||
|
clientFunc: () => Client4.getUserPreferences(userID),
|
||||||
|
onSuccess: PreferenceTypes.RECEIVED_USER_ALL_PREFERENCES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function setActionsMenuInitialisationState(initializationState: Record<string, boolean>): ThunkActionFunc<void> {
|
export function setActionsMenuInitialisationState(initializationState: Record<string, boolean>): ThunkActionFunc<void> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@@ -77,11 +85,15 @@ export function setCustomStatusInitialisationState(initializationState: Record<s
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function savePreferences(userId: string, preferences: PreferenceType[]): ActionFuncAsync {
|
export function savePreferences(userId: string, preferences: PreferenceType[]): ActionFuncAsync {
|
||||||
return async (dispatch) => {
|
return async (dispatch, getState) => {
|
||||||
(async function savePreferencesWrapper() {
|
(async function savePreferencesWrapper() {
|
||||||
|
const state = getState();
|
||||||
|
const currentUserId = getCurrentUserId(state);
|
||||||
|
const actionType = userId === currentUserId ? PreferenceTypes.RECEIVED_PREFERENCES : PreferenceTypes.RECEIVED_USER_PREFERENCES;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
type: actionType,
|
||||||
data: preferences,
|
data: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import type {AnyAction} from 'redux';
|
import type {AnyAction} from 'redux';
|
||||||
import {combineReducers} from 'redux';
|
import {combineReducers} from 'redux';
|
||||||
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import {PreferenceTypes, UserTypes} from 'mattermost-redux/action_types';
|
import {PreferenceTypes, UserTypes} from 'mattermost-redux/action_types';
|
||||||
|
|
||||||
@@ -24,6 +24,24 @@ function setAllPreferences(preferences: PreferenceType[]): any {
|
|||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAllUserPreferences(preferences: PreferenceType[]): {[key: string]: PreferencesType} {
|
||||||
|
const nextState: {[key: string]: PreferencesType} = {};
|
||||||
|
if (preferences.length === 0) {
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userID = preferences[0].user_id;
|
||||||
|
nextState[userID] = {};
|
||||||
|
|
||||||
|
if (preferences) {
|
||||||
|
for (const preference of preferences) {
|
||||||
|
nextState[userID][getKey(preference)] = preference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
function myPreferences(state: Record<string, PreferenceType> = {}, action: AnyAction) {
|
function myPreferences(state: Record<string, PreferenceType> = {}, action: AnyAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case PreferenceTypes.RECEIVED_ALL_PREFERENCES:
|
case PreferenceTypes.RECEIVED_ALL_PREFERENCES:
|
||||||
@@ -62,8 +80,37 @@ function myPreferences(state: Record<string, PreferenceType> = {}, action: AnyAc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userPreferences(state: Record<string, PreferencesType> = {}, action: AnyAction) {
|
||||||
|
switch (action.type) {
|
||||||
|
case PreferenceTypes.RECEIVED_USER_ALL_PREFERENCES:
|
||||||
|
return setAllUserPreferences(action.data);
|
||||||
|
|
||||||
|
case PreferenceTypes.RECEIVED_USER_PREFERENCES: {
|
||||||
|
const nextState = {...state};
|
||||||
|
|
||||||
|
const data = action.data as PreferenceType[];
|
||||||
|
if (action.data && data.length > 0) {
|
||||||
|
const userID = data[0].user_id;
|
||||||
|
nextState[userID] = nextState[userID] ? {...nextState[userID]} : {};
|
||||||
|
|
||||||
|
for (const preference of action.data) {
|
||||||
|
nextState[preference.user_id][getKey(preference)] = preference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UserTypes.LOGOUT_SUCCESS:
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
|
||||||
// object where the key is the category-name and has the corresponding value
|
// object where the key is the category-name and has the corresponding value
|
||||||
myPreferences,
|
myPreferences,
|
||||||
|
userPreferences,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {CollapsedThreads} from '@mattermost/types/config';
|
import {CollapsedThreads} from '@mattermost/types/config';
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
|
||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
import {General, Preferences} from 'mattermost-redux/constants';
|
import {General, Preferences} from 'mattermost-redux/constants';
|
||||||
@@ -16,6 +16,10 @@ export function getMyPreferences(state: GlobalState): { [x: string]: PreferenceT
|
|||||||
return state.entities.preferences.myPreferences;
|
return state.entities.preferences.myPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUserPreferences(state: GlobalState, userID: string): { [x: string]: PreferenceType } {
|
||||||
|
return state.entities.preferences.userPreferences[userID];
|
||||||
|
}
|
||||||
|
|
||||||
export function get(state: GlobalState, category: string, name: string, defaultValue: any = '') {
|
export function get(state: GlobalState, category: string, name: string, defaultValue: any = '') {
|
||||||
const key = getPreferenceKey(category, name);
|
const key = getPreferenceKey(category, name);
|
||||||
const prefs = getMyPreferences(state);
|
const prefs = getMyPreferences(state);
|
||||||
@@ -27,11 +31,26 @@ export function get(state: GlobalState, category: string, name: string, defaultV
|
|||||||
return prefs[key].value;
|
return prefs[key].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFromPreferences(preferences: PreferencesType, category: string, name: string, defaultValue: any = '') {
|
||||||
|
const key = getPreferenceKey(category, name);
|
||||||
|
|
||||||
|
if (!(key in preferences)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preferences[key].value;
|
||||||
|
}
|
||||||
|
|
||||||
export function getBool(state: GlobalState, category: string, name: string, defaultValue = false): boolean {
|
export function getBool(state: GlobalState, category: string, name: string, defaultValue = false): boolean {
|
||||||
const value = get(state, category, name, String(defaultValue));
|
const value = get(state, category, name, String(defaultValue));
|
||||||
return value !== 'false';
|
return value !== 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBoolFromPreferences(userPreferences: PreferencesType, category: string, name: string, defaultValue = false): boolean {
|
||||||
|
const value = getFromPreferences(userPreferences, category, name, String(defaultValue));
|
||||||
|
return value !== 'false';
|
||||||
|
}
|
||||||
|
|
||||||
export function getInt(state: GlobalState, category: string, name: string, defaultValue = 0): number {
|
export function getInt(state: GlobalState, category: string, name: string, defaultValue = 0): number {
|
||||||
const value = get(state, category, name, defaultValue);
|
const value = get(state, category, name, defaultValue);
|
||||||
return parseInt(value, 10);
|
return parseInt(value, 10);
|
||||||
@@ -57,6 +76,26 @@ export function makeGetCategory(): (state: GlobalState, category: string) => Pre
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeGetUserCategory(userID: string): (state: GlobalState, category: string) => PreferenceType[] {
|
||||||
|
return createSelector(
|
||||||
|
'makeGetCategory',
|
||||||
|
(state) => getUserPreferences(state, userID),
|
||||||
|
(state: GlobalState, category: string) => category,
|
||||||
|
(preferences, category) => {
|
||||||
|
const prefix = category + '--';
|
||||||
|
const prefsInCategory: PreferenceType[] = [];
|
||||||
|
|
||||||
|
for (const key in preferences) {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
prefsInCategory.push(preferences[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefsInCategory;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getDirectShowCategory = makeGetCategory();
|
const getDirectShowCategory = makeGetCategory();
|
||||||
|
|
||||||
export function getDirectShowPreferences(state: GlobalState) {
|
export function getDirectShowPreferences(state: GlobalState) {
|
||||||
@@ -180,31 +219,44 @@ export function makeGetStyleFromTheme<Style>(): (state: GlobalState, getStyleFro
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calculateUserShouldShowUnreadsCategory(userPreferences: PreferencesType, serverDefault?: string): boolean {
|
||||||
|
const userPreference = getFromPreferences(userPreferences, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.SHOW_UNREAD_SECTION);
|
||||||
|
const oldUserPreference = getFromPreferences(userPreferences, Preferences.CATEGORY_SIDEBAR_SETTINGS, '');
|
||||||
|
|
||||||
|
return calculateShouldShowUnreadsCategory(userPreference, oldUserPreference, serverDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateShouldShowUnreadsCategory(userPreference: string, oldUserPreference: string, serverDefault?: string): boolean {
|
||||||
|
// Prefer the show_unread_section user preference over the previous version
|
||||||
|
if (userPreference) {
|
||||||
|
return userPreference === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldUserPreference) {
|
||||||
|
return JSON.parse(oldUserPreference).unreads_at_top === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user setting is not set, so use the system default
|
||||||
|
return serverDefault === General.DEFAULT_ON;
|
||||||
|
}
|
||||||
|
|
||||||
// shouldShowUnreadsCategory returns true if the user has unereads grouped separately with the new sidebar enabled.
|
// shouldShowUnreadsCategory returns true if the user has unereads grouped separately with the new sidebar enabled.
|
||||||
export const shouldShowUnreadsCategory: (state: GlobalState) => boolean = createSelector(
|
export const shouldShowUnreadsCategory: (state: GlobalState) => boolean = createSelector(
|
||||||
'shouldShowUnreadsCategory',
|
'shouldShowUnreadsCategory',
|
||||||
(state: GlobalState) => get(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.SHOW_UNREAD_SECTION),
|
(state: GlobalState) => get(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.SHOW_UNREAD_SECTION),
|
||||||
(state: GlobalState) => get(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, ''),
|
(state: GlobalState) => get(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, ''),
|
||||||
(state: GlobalState) => getConfig(state).ExperimentalGroupUnreadChannels,
|
(state: GlobalState) => getConfig(state).ExperimentalGroupUnreadChannels,
|
||||||
(userPreference, oldUserPreference, serverDefault) => {
|
calculateShouldShowUnreadsCategory,
|
||||||
// Prefer the show_unread_section user preference over the previous version
|
|
||||||
if (userPreference) {
|
|
||||||
return userPreference === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldUserPreference) {
|
|
||||||
return JSON.parse(oldUserPreference).unreads_at_top === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user setting is not set, so use the system default
|
|
||||||
return serverDefault === General.DEFAULT_ON;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export function getUnreadScrollPositionPreference(state: GlobalState): string {
|
export function getUnreadScrollPositionPreference(state: GlobalState): string {
|
||||||
return get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.UNREAD_SCROLL_POSITION, Preferences.UNREAD_SCROLL_POSITION_START_FROM_LEFT);
|
return get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.UNREAD_SCROLL_POSITION, Preferences.UNREAD_SCROLL_POSITION_START_FROM_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUnreadScrollPositionFromPreference(userPreferences: PreferencesType): string {
|
||||||
|
return getFromPreferences(userPreferences, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.UNREAD_SCROLL_POSITION, Preferences.UNREAD_SCROLL_POSITION_START_FROM_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
export function getCollapsedThreadsPreference(state: GlobalState): string {
|
export function getCollapsedThreadsPreference(state: GlobalState): string {
|
||||||
const configValue = getConfig(state)?.CollapsedThreads;
|
const configValue = getConfig(state)?.CollapsedThreads;
|
||||||
let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF;
|
let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF;
|
||||||
@@ -221,6 +273,22 @@ export function getCollapsedThreadsPreference(state: GlobalState): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCollapsedThreadsPreferenceFromPreferences(state: GlobalState, userPreferences: PreferencesType): string {
|
||||||
|
const configValue = getConfig(state)?.CollapsedThreads;
|
||||||
|
let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF;
|
||||||
|
|
||||||
|
if (configValue === CollapsedThreads.DEFAULT_ON || configValue === CollapsedThreads.ALWAYS_ON) {
|
||||||
|
preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFromPreferences(
|
||||||
|
userPreferences,
|
||||||
|
Preferences.CATEGORY_DISPLAY_SETTINGS,
|
||||||
|
Preferences.COLLAPSED_REPLY_THREADS,
|
||||||
|
preferenceDefault,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function isCollapsedThreadsAllowed(state: GlobalState): boolean {
|
export function isCollapsedThreadsAllowed(state: GlobalState): boolean {
|
||||||
return Boolean(getConfig(state)) && getConfig(state).CollapsedThreads !== undefined && getConfig(state).CollapsedThreads !== CollapsedThreads.DISABLED;
|
return Boolean(getConfig(state)) && getConfig(state).CollapsedThreads !== undefined && getConfig(state).CollapsedThreads !== CollapsedThreads.DISABLED;
|
||||||
}
|
}
|
||||||
@@ -232,6 +300,13 @@ export function isCollapsedThreadsEnabled(state: GlobalState): boolean {
|
|||||||
return isAllowed && (userPreference === Preferences.COLLAPSED_REPLY_THREADS_ON || getConfig(state).CollapsedThreads === CollapsedThreads.ALWAYS_ON);
|
return isAllowed && (userPreference === Preferences.COLLAPSED_REPLY_THREADS_ON || getConfig(state).CollapsedThreads === CollapsedThreads.ALWAYS_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCollapsedThreadsEnabledForUser(state: GlobalState, userPreferences: PreferencesType): boolean {
|
||||||
|
const isAllowed = isCollapsedThreadsAllowed(state);
|
||||||
|
const userPreference = getCollapsedThreadsPreferenceFromPreferences(state, userPreferences);
|
||||||
|
|
||||||
|
return isAllowed && (userPreference === Preferences.COLLAPSED_REPLY_THREADS_ON || getConfig(state).CollapsedThreads === CollapsedThreads.ALWAYS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
export function isGroupChannelManuallyVisible(state: GlobalState, channelId: string): boolean {
|
export function isGroupChannelManuallyVisible(state: GlobalState, channelId: string): boolean {
|
||||||
return getBool(state, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, false);
|
return getBool(state, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, false);
|
||||||
}
|
}
|
||||||
@@ -264,6 +339,12 @@ export function getVisibleDmGmLimit(state: GlobalState) {
|
|||||||
return getInt(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.LIMIT_VISIBLE_DMS_GMS, defaultLimit);
|
return getInt(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.LIMIT_VISIBLE_DMS_GMS, defaultLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUserVisibleDmGmLimit(userPreferences: PreferencesType) {
|
||||||
|
const defaultLimit = 40;
|
||||||
|
const value = getFromPreferences(userPreferences, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.LIMIT_VISIBLE_DMS_GMS, defaultLimit);
|
||||||
|
return parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
export function onboardingTourTipsEnabled(state: GlobalState): boolean {
|
export function onboardingTourTipsEnabled(state: GlobalState): boolean {
|
||||||
return getFeatureFlagValue(state, 'OnboardingTourTips') === 'true';
|
return getFeatureFlagValue(state, 'OnboardingTourTips') === 'true';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {getTimezoneLabel, getUserCurrentTimezone} from 'mattermost-redux/utils/t
|
|||||||
|
|
||||||
import {getCurrentUser} from './common';
|
import {getCurrentUser} from './common';
|
||||||
|
|
||||||
function getTimezoneForUserProfile(profile: UserProfile) {
|
export function getTimezoneForUserProfile(profile: UserProfile) {
|
||||||
if (profile && profile.timezone) {
|
if (profile && profile.timezone) {
|
||||||
return {
|
return {
|
||||||
...profile.timezone,
|
...profile.timezone,
|
||||||
@@ -41,14 +41,16 @@ export const getCurrentTimezone = createSelector(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function generateCurrentTimezoneLabel(timezone: string) {
|
||||||
|
if (!timezone) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTimezoneLabel(timezones, timezone);
|
||||||
|
}
|
||||||
|
|
||||||
export const getCurrentTimezoneLabel = createSelector(
|
export const getCurrentTimezoneLabel = createSelector(
|
||||||
'getCurrentTimezoneLabel',
|
'getCurrentTimezoneLabel',
|
||||||
getCurrentTimezone,
|
getCurrentTimezone,
|
||||||
(timezone) => {
|
generateCurrentTimezoneLabel,
|
||||||
if (!timezone) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return getTimezoneLabel(timezones, timezone);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ const state: GlobalState = {
|
|||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
myPreferences: {},
|
myPreferences: {},
|
||||||
|
userPreferences: {},
|
||||||
},
|
},
|
||||||
bots: {
|
bots: {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
|
|||||||
@@ -318,10 +318,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
|
width: 100%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: v(center-channel-color);
|
color: v(center-channel-color);
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,36 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.loadingIndicator {
|
||||||
|
display: block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 36px auto 48px;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
|
|
||||||
|
.adminModeBadge {
|
||||||
|
display: flex;
|
||||||
|
width: 86px;
|
||||||
|
min-width: 86px;
|
||||||
|
height: 22px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px var(--spacing-xxxs, 6px);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-left: 24px;
|
||||||
|
background: var(--error-text);
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
@@ -189,6 +219,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
|
min-height: 475px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -203,11 +234,11 @@
|
|||||||
|
|
||||||
.settings-links {
|
.settings-links {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 180px;
|
width: 232px;
|
||||||
background: var(--sidebar-bg);
|
padding: 16px;
|
||||||
|
background: rgba(var(--center-channel-color-rgb), 0.04);
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
position: fixed;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,11 +470,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.divider-dark {
|
.divider-dark {
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-light {
|
.divider-light {
|
||||||
border-bottom: 1px solid lightgrey;
|
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
|
|
||||||
& + .divider-light {
|
& + .divider-light {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -530,23 +561,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-pills > li button {
|
.nav-pills > li button {
|
||||||
color: rgba(var(--sidebar-text-rgb), 0.75);
|
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-pills {
|
.nav-pills {
|
||||||
> li {
|
> li {
|
||||||
margin: 0;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 15px;
|
padding: 6px 15px;
|
||||||
border-radius: 0;
|
border-radius: 4px;
|
||||||
color: $gray;
|
color: $gray;
|
||||||
|
font-weight: 600;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(var(--center-channel-color-rgb), 0.04);
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -565,14 +602,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
button,
|
|
||||||
button:hover,
|
|
||||||
button:focus {
|
|
||||||
background: var(--sidebar-text-hover-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
div {
|
div {
|
||||||
background-color: #e1e1e1;
|
background-color: #e1e1e1;
|
||||||
@@ -596,7 +625,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@include alpha-property(background-color, $black, 0.1);
|
background: rgba(var(--button-bg-rgb), 0.08);
|
||||||
|
color: v(button-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
|
||||||
|
import Permissions from 'mattermost-redux/constants/permissions';
|
||||||
import {ResourceToSysConsolePermissionsTable, RESOURCE_KEYS} from 'mattermost-redux/constants/permissions_sysconsole';
|
import {ResourceToSysConsolePermissionsTable, RESOURCE_KEYS} from 'mattermost-redux/constants/permissions_sysconsole';
|
||||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||||
import {getMySystemPermissions} from 'mattermost-redux/selectors/entities/roles_helpers';
|
import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {getMySystemPermissions, haveISystemPermission} from 'mattermost-redux/selectors/entities/roles_helpers';
|
||||||
|
|
||||||
import AdminDefinition from 'components/admin_console/admin_definition';
|
import AdminDefinition from 'components/admin_console/admin_definition';
|
||||||
|
|
||||||
|
import {isEnterpriseOrE20License} from '../utils/license_utils';
|
||||||
|
|
||||||
export const getAdminDefinition = createSelector(
|
export const getAdminDefinition = createSelector(
|
||||||
'getAdminDefinition',
|
'getAdminDefinition',
|
||||||
() => AdminDefinition,
|
() => AdminDefinition,
|
||||||
@@ -49,3 +53,29 @@ export const getConsoleAccess = createSelector(
|
|||||||
return consoleAccess;
|
return consoleAccess;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getShowManageUserSettings = createSelector(
|
||||||
|
'showManageUserSettings',
|
||||||
|
getLicense,
|
||||||
|
(state) => state,
|
||||||
|
(license, state) => {
|
||||||
|
const hasWriteUserManagementPermission = haveISystemPermission(state, {permission: Permissions.SYSCONSOLE_WRITE_USERMANAGEMENT_USERS});
|
||||||
|
|
||||||
|
const isEnterprise = isEnterpriseOrE20License(license);
|
||||||
|
|
||||||
|
return hasWriteUserManagementPermission && isEnterprise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getShowLockedManageUserSettings = createSelector(
|
||||||
|
'showLockedManageUserSettings',
|
||||||
|
getLicense,
|
||||||
|
(state) => state,
|
||||||
|
(license, state) => {
|
||||||
|
const hasWriteUserManagementPermission = haveISystemPermission(state, {permission: Permissions.SYSCONSOLE_WRITE_USERMANAGEMENT_USERS});
|
||||||
|
|
||||||
|
const isEnterprise = isEnterpriseOrE20License(license);
|
||||||
|
|
||||||
|
return hasWriteUserManagementPermission && !isEnterprise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -461,6 +461,7 @@ export const ModalIdentifiers = {
|
|||||||
EXPORT_ERROR_MODAL: 'export_error_modal',
|
EXPORT_ERROR_MODAL: 'export_error_modal',
|
||||||
CHANNEL_BOOKMARK_DELETE: 'channel_bookmark_delete',
|
CHANNEL_BOOKMARK_DELETE: 'channel_bookmark_delete',
|
||||||
CHANNEL_BOOKMARK_CREATE: 'channel_bookmark_create',
|
CHANNEL_BOOKMARK_CREATE: 'channel_bookmark_create',
|
||||||
|
CONFIRM_MANAGE_USER_SETTINGS_MODAL: 'confirm_switch_to_settings',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserStatuses = {
|
export const UserStatuses = {
|
||||||
|
|||||||
@@ -2491,6 +2491,13 @@ export default class Client4 {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getUserPreferences = (userId: string) => {
|
||||||
|
return this.doFetch<PreferenceType[]>(
|
||||||
|
`${this.getPreferencesRoute(userId)}`,
|
||||||
|
{method: 'get'},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
deletePreferences = (userId: string, preferences: PreferenceType[]) => {
|
deletePreferences = (userId: string, preferences: PreferenceType[]) => {
|
||||||
return this.doFetch<StatusOK>(
|
return this.doFetch<StatusOK>(
|
||||||
`${this.getPreferencesRoute(userId)}/delete`,
|
`${this.getPreferencesRoute(userId)}/delete`,
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ export type GlobalState = {
|
|||||||
myPreferences: {
|
myPreferences: {
|
||||||
[x: string]: PreferenceType;
|
[x: string]: PreferenceType;
|
||||||
};
|
};
|
||||||
|
userPreferences: {
|
||||||
|
[userID: string]: {
|
||||||
|
[x: string]: PreferenceType;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
admin: AdminState;
|
admin: AdminState;
|
||||||
jobs: JobsState;
|
jobs: JobsState;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user