PLT-4332 Position field for Users (#4632)
* Add User.Position field to store & model. * GOFMT * Add Position to user settings. * Unit tests. * Add position to profile popup. * i18n * Fix log message for invalid position. * Add Position field attribute to LDAP config. * Add Position field attribute to SAML config. * Reword empty position message. * Change Position Max Length to 35. * Better invalid position error message. * Add new fields to config.json. * Ensure position is never longer than max when displayed. * Hard limit of 64 chars with soft limit still 35 * Put field with other attributes.
Этот коммит содержится в:
коммит произвёл
enahum
родитель
9735854503
Коммит
8406e854aa
@@ -38,6 +38,7 @@ export default class LdapSettings extends AdminSettings {
|
||||
config.LdapSettings.NicknameAttribute = this.state.nicknameAttribute;
|
||||
config.LdapSettings.EmailAttribute = this.state.emailAttribute;
|
||||
config.LdapSettings.UsernameAttribute = this.state.usernameAttribute;
|
||||
config.LdapSettings.PositionAttribute = this.state.positionAttribute;
|
||||
config.LdapSettings.IdAttribute = this.state.idAttribute;
|
||||
config.LdapSettings.SyncIntervalMinutes = this.parseIntNonZero(this.state.syncIntervalMinutes);
|
||||
config.LdapSettings.SkipCertificateVerification = this.state.skipCertificateVerification;
|
||||
@@ -63,6 +64,7 @@ export default class LdapSettings extends AdminSettings {
|
||||
nicknameAttribute: config.LdapSettings.NicknameAttribute,
|
||||
emailAttribute: config.LdapSettings.EmailAttribute,
|
||||
usernameAttribute: config.LdapSettings.UsernameAttribute,
|
||||
positionAttribute: config.LdapSettings.PositionAttribute,
|
||||
idAttribute: config.LdapSettings.IdAttribute,
|
||||
syncIntervalMinutes: config.LdapSettings.SyncIntervalMinutes,
|
||||
skipCertificateVerification: config.LdapSettings.SkipCertificateVerification,
|
||||
@@ -299,6 +301,25 @@ export default class LdapSettings extends AdminSettings {
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='positionAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.ldap.positionAttrTitle'
|
||||
defaultMessage='Position Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.ldap.positionAttrEx', 'E.g.: "title"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.ldap.positionAttrDesc'
|
||||
defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the position field in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.positionAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='emailAttribute'
|
||||
label={
|
||||
|
||||
@@ -41,6 +41,7 @@ export default class SamlSettings extends AdminSettings {
|
||||
config.SamlSettings.EmailAttribute = this.state.emailAttribute;
|
||||
config.SamlSettings.UsernameAttribute = this.state.usernameAttribute;
|
||||
config.SamlSettings.NicknameAttribute = this.state.nicknameAttribute;
|
||||
config.SamlSettings.PositionAttribute = this.state.positionAttribute;
|
||||
config.SamlSettings.LocaleAttribute = this.state.localeAttribute;
|
||||
config.SamlSettings.LoginButtonText = this.state.loginButtonText;
|
||||
|
||||
@@ -65,6 +66,7 @@ export default class SamlSettings extends AdminSettings {
|
||||
emailAttribute: settings.EmailAttribute,
|
||||
usernameAttribute: settings.UsernameAttribute,
|
||||
nicknameAttribute: settings.NicknameAttribute,
|
||||
positionAttribute: settings.PositionAttribute,
|
||||
localeAttribute: settings.LocaleAttribute,
|
||||
loginButtonText: settings.LoginButtonText
|
||||
};
|
||||
@@ -508,6 +510,25 @@ export default class SamlSettings extends AdminSettings {
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='positionAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.positionAttrTitle'
|
||||
defaultMessage='Position Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.positionAttrEx', 'E.g.: "Role"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.positionAttrDesc'
|
||||
defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the position of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.positionAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='localeAttribute'
|
||||
label={
|
||||
|
||||
@@ -182,6 +182,23 @@ export default class UserProfile extends React.Component {
|
||||
|
||||
dataContent.push(webrtc);
|
||||
|
||||
const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
|
||||
if (position) {
|
||||
dataContent.push(
|
||||
<div
|
||||
data-toggle='tooltip'
|
||||
title={position}
|
||||
key='user-popover-position'
|
||||
>
|
||||
<p
|
||||
className='text-nowrap'
|
||||
>
|
||||
{position}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
|
||||
dataContent.push(
|
||||
<div
|
||||
|
||||
@@ -69,6 +69,10 @@ const holders = defineMessages({
|
||||
close: {
|
||||
id: 'user.settings.general.close',
|
||||
defaultMessage: 'Close'
|
||||
},
|
||||
position: {
|
||||
id: 'user.settings.general.position',
|
||||
defaultMessage: 'Position'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,6 +89,7 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
this.submitEmail = this.submitEmail.bind(this);
|
||||
this.submitUser = this.submitUser.bind(this);
|
||||
this.submitPicture = this.submitPicture.bind(this);
|
||||
this.submitPosition = this.submitPosition.bind(this);
|
||||
|
||||
this.updateUsername = this.updateUsername.bind(this);
|
||||
this.updateFirstName = this.updateFirstName.bind(this);
|
||||
@@ -94,6 +99,7 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
this.updateConfirmEmail = this.updateConfirmEmail.bind(this);
|
||||
this.updatePicture = this.updatePicture.bind(this);
|
||||
this.updateSection = this.updateSection.bind(this);
|
||||
this.updatePosition = this.updatePosition.bind(this);
|
||||
|
||||
this.state = this.setupInitialState(props);
|
||||
}
|
||||
@@ -249,6 +255,22 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitPosition(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const user = Object.assign({}, this.props.user);
|
||||
const position = this.state.position.trim();
|
||||
|
||||
if (user.position === position) {
|
||||
this.updateSection('');
|
||||
return;
|
||||
}
|
||||
|
||||
user.position = position;
|
||||
|
||||
this.submitUser(user, Constants.UserUpdateEvents.Position, false);
|
||||
}
|
||||
|
||||
updateUsername(e) {
|
||||
this.setState({username: e.target.value});
|
||||
}
|
||||
@@ -265,6 +287,10 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
this.setState({nickname: e.target.value});
|
||||
}
|
||||
|
||||
updatePosition(e) {
|
||||
this.setState({position: e.target.value});
|
||||
}
|
||||
|
||||
updateEmail(e) {
|
||||
this.setState({email: e.target.value});
|
||||
}
|
||||
@@ -302,6 +328,7 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
nickname: user.nickname,
|
||||
position: user.position,
|
||||
email: user.email,
|
||||
confirmEmail: '',
|
||||
picture: null,
|
||||
@@ -936,6 +963,99 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let positionSection;
|
||||
if (this.props.activeSection === 'position') {
|
||||
let extraInfo;
|
||||
let submit = null;
|
||||
if ((this.props.user.auth_service === 'ldap' || this.props.user.auth_service === Constants.SAML_SERVICE) && global.window.mm_config.PositionAttributeSet === 'true') {
|
||||
extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.field_handled_externally'
|
||||
defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so though your login provider.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
let positionLabel = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.position'
|
||||
defaultMessage='Position'
|
||||
/>
|
||||
);
|
||||
if (Utils.isMobile()) {
|
||||
positionLabel = '';
|
||||
}
|
||||
|
||||
inputs.push(
|
||||
<div
|
||||
key='positionSetting'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{positionLabel}</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
type='text'
|
||||
onChange={this.updatePosition}
|
||||
value={this.state.position}
|
||||
maxLength={Constants.MAX_POSITION_LENGTH}
|
||||
autoCapitalize='off'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.positionExtra'
|
||||
defaultMessage='Tell your teammates what you do.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
submit = this.submitPosition;
|
||||
}
|
||||
|
||||
positionSection = (
|
||||
<SettingItemMax
|
||||
title={formatMessage(holders.position)}
|
||||
inputs={inputs}
|
||||
submit={submit}
|
||||
server_error={serverError}
|
||||
client_error={clientError}
|
||||
updateSection={(e) => {
|
||||
this.updateSection('');
|
||||
e.preventDefault();
|
||||
}}
|
||||
extraInfo={extraInfo}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
let describe = '';
|
||||
if (user.position) {
|
||||
describe = user.position;
|
||||
} else {
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emptyPosition'
|
||||
defaultMessage="Click 'Edit' to add your job title / position"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
positionSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.position)}
|
||||
describe={describe}
|
||||
updateSection={() => {
|
||||
this.updateSection('position');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const emailSection = this.createEmailSection();
|
||||
|
||||
let pictureSection;
|
||||
@@ -1030,6 +1150,8 @@ class UserSettingsGeneralTab extends React.Component {
|
||||
<div className='divider-light'/>
|
||||
{nicknameSection}
|
||||
<div className='divider-light'/>
|
||||
{positionSection}
|
||||
<div className='divider-light'/>
|
||||
{emailSection}
|
||||
<div className='divider-light'/>
|
||||
{pictureSection}
|
||||
|
||||
@@ -441,6 +441,9 @@
|
||||
"admin.ldap.portDesc": "The port Mattermost will use to connect to the AD/LDAP server. Default is 389.",
|
||||
"admin.ldap.portEx": "E.g.: \"389\"",
|
||||
"admin.ldap.portTitle": "AD/LDAP Port:",
|
||||
"admin.ldap.positionAttrEx": "E.g.: \"title\"",
|
||||
"admin.ldap.positionAttrDesc": "(Optional) The attribute in the AD/LDAP server that will be used to populate the position field in Mattermost.",
|
||||
"admin.ldap.positionAttrTitle": "Position Attribute:",
|
||||
"admin.ldap.queryDesc": "The timeout value for queries to the AD/LDAP server. Increase if you are getting timeout errors caused by a slow AD/LDAP server.",
|
||||
"admin.ldap.queryEx": "E.g.: \"60\"",
|
||||
"admin.ldap.queryTitle": "Query Timeout (seconds):",
|
||||
@@ -614,6 +617,9 @@
|
||||
"admin.saml.nicknameAttrDesc": "(Optional) The attribute in the SAML Assertion that will be used to populate the nickname of users in Mattermost.",
|
||||
"admin.saml.nicknameAttrEx": "E.g.: \"Nickname\"",
|
||||
"admin.saml.nicknameAttrTitle": "Nickname Attribute:",
|
||||
"admin.saml.positionAttrDesc": "(Optional) The attribute in the SAML Assertion that will be used to populate the position of users in Mattermost.",
|
||||
"admin.saml.positionAttrEx": "E.g.: \"Role\"",
|
||||
"admin.saml.positionAttrTitle": "Position Attribute:",
|
||||
"admin.saml.privateKeyFileFileDesc": "The private key used to decrypt SAML Assertions from the Identity Provider.",
|
||||
"admin.saml.privateKeyFileFileRemoveDesc": "Remove the private key used to decrypt SAML Assertions from the Identity Provider.",
|
||||
"admin.saml.privateKeyFileTitle": "Service Provider Private Key:",
|
||||
@@ -1953,6 +1959,7 @@
|
||||
"user.settings.general.emailUnchanged": "Your new email address is the same as your old email address.",
|
||||
"user.settings.general.emptyName": "Click 'Edit' to add your full name",
|
||||
"user.settings.general.emptyNickname": "Click 'Edit' to add a nickname",
|
||||
"user.settings.general.emptyPosition": "Click 'Edit' to add your job title / position",
|
||||
"user.settings.general.field_handled_externally": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
|
||||
"user.settings.general.firstName": "First Name",
|
||||
"user.settings.general.fullName": "Full Name",
|
||||
@@ -1969,6 +1976,8 @@
|
||||
"user.settings.general.nicknameExtra": "Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.",
|
||||
"user.settings.general.notificationsExtra": "By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.",
|
||||
"user.settings.general.notificationsLink": "Notifications",
|
||||
"user.settings.general.position": "Position",
|
||||
"user.settings.general.positionExtra": "Tell your teammates what you do.",
|
||||
"user.settings.general.primaryEmail": "Primary Email",
|
||||
"user.settings.general.profilePicture": "Profile Picture",
|
||||
"user.settings.general.title": "General Settings",
|
||||
|
||||
@@ -264,7 +264,8 @@ export const Constants = {
|
||||
FULLNAME: 'fullname',
|
||||
NICKNAME: 'nickname',
|
||||
EMAIL: 'email',
|
||||
LANGUAGE: 'language'
|
||||
LANGUAGE: 'language',
|
||||
POSITION: 'position'
|
||||
},
|
||||
|
||||
ScrollTypes: {
|
||||
@@ -835,6 +836,7 @@ export const Constants = {
|
||||
MAX_NICKNAME_LENGTH: 22,
|
||||
MIN_PASSWORD_LENGTH: 5,
|
||||
MAX_PASSWORD_LENGTH: 64,
|
||||
MAX_POSITION_LENGTH: 35,
|
||||
MIN_TRIGGER_LENGTH: 1,
|
||||
MAX_TRIGGER_LENGTH: 128,
|
||||
MAX_TEXTSETTING_LENGTH: 1024,
|
||||
|
||||
Ссылка в новой задаче
Block a user