Browse Source

UI-3174: Create voicemail box optionally on user creation (#91)

* Add checkbox to create vmbox on user creation form

* Use waterfall flow for user creation

* Extract user create api call to new function

* Create vmbox only if requested

* Rename view data property to create vmbox

* Fix waterfall callbacks for user creation flow

* Add translations

* Refactor code and remove vmbox from callflow

* Do not show VMBox row field if there is none for user

* Do not create VMBox at usersSmartUpdateVMBox, if user has none

* Add gittatributes to use default line ending behavior

* Apply suggestions according to code review

* Code refactor according to review
4.3
Guillermo Gutiérrez 7 years ago
committed by GitHub
parent
commit
4de21b7714
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 297 additions and 221 deletions
  1. +2
    -0
      .gitattributes
  2. +1
    -0
      i18n/en-US.json
  3. +114
    -51
      submodules/users/users.js
  4. +8
    -0
      submodules/users/views/creation.html
  5. +172
    -170
      submodules/users/views/name.html

+ 2
- 0
.gitattributes View File

@ -0,0 +1,2 @@
# Set the default behavior, in case people don't have core.autocrlf set
* text=auto

+ 1
- 0
i18n/en-US.json View File

@ -481,6 +481,7 @@
},
"dialogCreationUser": {
"createUser": "Create User",
"createVmbox": "Create a voicemail box for this user",
"errorOnCreation": "An error occured while trying to create this user:",
"extension": "Main Extension Number",
"extensionAlreadyExist": "This Extension is already taken, please choose a different Extension Number.",


+ 114
- 51
submodules/users/users.js View File

@ -1832,6 +1832,7 @@ define(function(require) {
sendToSameEmail: true,
nextExtension: '',
listExtensions: {},
createVmbox: true,
listVMBoxes: {}
},
arrayExtensions = [],
@ -3611,6 +3612,12 @@ define(function(require) {
delete formattedData.user.service;
}
if (!data.extra.createVmbox) {
// Remove vmbox from formatted data and user callflow
delete formattedData.vmbox;
delete formattedData.callflow.flow.children._;
}
delete formattedData.user.extra;
return formattedData;
@ -3839,37 +3846,63 @@ define(function(require) {
usersCreate: function(data, success, error) {
var self = this;
self.callApi({
resource: 'user.create',
data: {
accountId: self.accountId,
data: data.user
monster.waterfall([
function(callback) {
self.usersCreateUser({
data: {
data: data.user
},
success: function(_dataUser) {
data.user.id = _dataUser.id;
callback(null, _dataUser);
},
error: function(parsedError) {
callback(true);
},
onChargesCancelled: function() {
callback(true);
}
});
},
success: function(_dataUser) {
var userId = _dataUser.data.id;
data.user.id = userId;
data.vmbox.owner_id = userId;
function(_dataUser, callback) {
if (!data.extra.createVmbox) {
callback(null, _dataUser);
return;
}
data.vmbox.owner_id = _dataUser.id;
self.usersCreateVMBox(data.vmbox, function(_dataVM) {
data.callflow.owner_id = userId;
data.callflow.type = 'mainUserCallflow';
data.callflow.flow.data.id = userId;
data.callflow.flow.children._.data.id = _dataVM.id;
self.usersCreateCallflow(data.callflow, function(_dataCF) {
if (data.extra.includeInDirectory) {
self.usersAddUserToMainDirectory(_dataUser.data, _dataCF.id, function(dataDirectory) {
success(data);
});
} else {
success(data);
}
});
callback(null, _dataUser);
});
},
error: function() {
function(_dataUser, callback) {
var userId = _dataUser.id;
data.callflow.owner_id = userId;
data.callflow.type = 'mainUserCallflow';
data.callflow.flow.data.id = userId;
self.usersCreateCallflow(data.callflow, function(_dataCF) {
callback(null, _dataUser, _dataCF);
});
},
function(_dataUser, _dataCF, callback) {
if (!data.extra.includeInDirectory) {
callback(null);
return;
}
self.usersAddUserToMainDirectory(_dataUser, _dataCF.id, function(dataDirectory) {
callback(null);
});
}
],
function(err) {
if (err) {
error();
return;
}
success(data);
});
},
@ -3915,7 +3948,12 @@ define(function(require) {
user: user,
needVMUpdate: false,
callback: function(_dataVM) {
callflow.flow.children._.data.id = _dataVM.id;
if (_.isEmpty(_dataVM)) {
// Remove VMBox from callflow if there is none
callflow.flow.children = {};
} else {
callflow.flow.children._.data.id = _dataVM.id;
}
self.usersCreateCallflow(callflow,
function(_dataCF) {
@ -4849,39 +4887,40 @@ define(function(require) {
user = args.user,
needVMUpdate = args.needVMUpdate || true,
callback = args.callback,
oldPresenceId = args.oldPresenceId || undefined,
userExtension = args.userExtension;
oldPresenceId = args.oldPresenceId || undefined;
self.usersListVMBoxesUser(user.id, function(vmboxes) {
if (vmboxes.length > 0) {
if (needVMUpdate) {
if (_.isEmpty(vmboxes)) {
callback && callback({});
return;
}
if (!needVMUpdate) {
callback && callback(vmboxes[0]);
return;
}
monster.waterfall([
function(wfCallback) {
self.usersGetVMBox(vmboxes[0].id, function(vmbox) {
vmbox.name = user.first_name + ' ' + user.last_name + self.appFlags.users.smartPBXVMBoxString;
// We only want to update the vmbox number if it was already synced with the presenceId (and if the presenceId was not already set)
// This allows us to support old clients who have mailbox number != than their extension number
if (oldPresenceId === vmbox.mailbox) {
// If it's synced, then we update the vmbox number as long as the main extension is set to something different than 'unset' in which case we don't update the vmbox number value
vmbox.mailbox = (user.presence_id && user.presence_id !== 'unset') ? user.presence_id + '' : vmbox.mailbox;
}
wfCallback(null, vmbox);
});
},
function(vmbox, wfCallback) {
vmbox.name = user.first_name + ' ' + user.last_name + self.appFlags.users.smartPBXVMBoxString;
// We only want to update the vmbox number if it was already synced with the presenceId (and if the presenceId was not already set)
// This allows us to support old clients who have mailbox number != than their extension number
if (oldPresenceId === vmbox.mailbox) {
// If it's synced, then we update the vmbox number as long as the main extension is set to something different than 'unset' in which case we don't update the vmbox number value
vmbox.mailbox = (user.presence_id && user.presence_id !== 'unset') ? user.presence_id + '' : vmbox.mailbox;
}
self.usersUpdateVMBox(vmbox, function(vmboxSaved) {
callback && callback(vmboxSaved);
});
self.usersUpdateVMBox(vmbox, function(vmboxSaved) {
wfCallback(null, vmboxSaved);
});
} else {
callback && callback(vmboxes[0]);
}
} else {
var vmbox = {
owner_id: user.id,
mailbox: user.presence_id || userExtension || user.extra.vmbox.mailbox,
name: user.first_name + ' ' + user.last_name + self.appFlags.users.smartPBXVMBoxString
};
self.usersCreateVMBox(vmbox, function(vmbox) {
callback && callback(vmbox);
});
}
], function(err, vmboxSaved) {
callback && callback(vmboxSaved);
});
});
},
@ -5197,6 +5236,30 @@ define(function(require) {
callback && callback(data.data);
}
});
},
usersCreateUser: function(args) {
var self = this;
self.callApi({
resource: 'user.create',
data: _.merge({
accountId: self.accountId
}, args.data),
success: function(data, status) {
args.hasOwnProperty('success') && args.success(data.data);
},
error: function(parsedError) {
if (parsedError.error === '402') {
return;
}
args.hasOwnProperty('error') && args.error(parsedError);
},
onChargesCancelled: function() {
args.hasOwnProperty('onChargesCancelled') && args.onChargesCancelled();
}
});
}
};


+ 8
- 0
submodules/users/views/creation.html View File

@ -53,6 +53,14 @@
</div>
<div>
<div class="control-group hack-left">
<label class="control-label" for="create_vmbox"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.createVmbox }}
<input id="create_vmbox" type="checkbox" name="extra.createVmbox"{{#if createVmbox}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</div>
</div>
<div class="control-group hack-left">
<label class="control-label" for="include_directory"></label>
<div class="controls">


+ 172
- 170
submodules/users/views/name.html View File

@ -1,170 +1,172 @@
<div class="detail-user">
<form id="form-{{id}}" class="user-fields row-fluid">
<div class="basic-fields span6">
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.nameHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-user"></i></div>
<input required type="text" class="input-small" name="first_name" value="{{first_name}}" placeholder="{{i18n.users.editionForm.firstName}}"></input><input required class="input-small fix-left" type="text" name="last_name" value="{{last_name}}" placeholder="{{i18n.users.editionForm.lastName}}"></input>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.loginHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-key"></i></div>
<span id="username">{{username}}</span>
<a href="javascript:void(0);" id="change_username" class="monster-link blue">{{i18n.users.editionForm.changeUsername}}</a>
</div>
<div class="row-fields vmbox">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.vmboxHelp}}" data-placement="top" data-toggle="tooltip"><i class="icon-telicon-voicemail"></i></div>
<label for="vmbox_number">
{{ i18n.users.editionForm.vmboxNumber }}
</label>
{{extra.vmbox.mailbox}}
<a href="javascript:void(0);" id="change_pin" class="monster-link blue">{{i18n.users.editionForm.changePIN}}</a>
</div>
<div class="email-border{{#if extra.differentEmail }} open{{/if}}">
<div class="row-fields email-checkbox">
<label class="fix-left">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendToDifferentEmail }}
<input id="notification_email" type="checkbox" name="extra.differentEmail"{{#if extra.differentEmail}} checked{{/if}}></input>
{{/monsterCheckbox}}
</label>
</div>
<div class="row-fields email-group">
<div class="fa fa-wrapper"><i class="fa fa-envelope"></i></div>
<input type="email" name="extra.email" id="email" placeholder="{{i18n.users.dialogCreationUser.notificationEmail}}"{{#if extra.differentEmail}} value="{{email}}"{{/if}}>
</div>
</div>
{{#if extra.mainCallflowId}}
<div class="row-fields">
<label class="fix-left">
{{#monsterCheckbox i18n.users.includeInDirectory }}
<input id="include_directory" type="checkbox" name="extra.includeInDirectory"{{#if extra.includeInDirectory}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</label>
</div>
{{/if}}
</div>
<div class="basic-fields span6">
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.roleHelp}}" data-placement="top" data-toggle="tooltip"><i class="icon-telicon-moderator"></i></div>
<select id="priv_level" name="priv_level">
{{#compare priv_level '===' 'admin'}}
<option value="admin" selected>{{ i18n.users.admin }}</option>
<option value="user">{{ i18n.users.user }}</option>
{{else}}
<option value="admin">{{ i18n.users.admin }}</option>
<option value="user" selected>{{ i18n.users.user }}</option>
{{/compare}}
</select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.timezoneHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-clock-o"></i></div>
<select id="user_timezone" name="timezone" data-original_value="{{timezone}}"></select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.languageHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-flag"></i></div>
<select id="user_language" name="extra.language">
{{#select language}}
<option value="auto">{{i18n.languages.auto}}</option>
<option value="en-US">{{i18n.languages.americanEnglish}}</option>
<option value="fr-FR">{{i18n.languages.frenchFrench}}</option>
<option value="de-DE">{{i18n.languages.germanGerman}}</option>
<option value="ru-RU">{{i18n.languages.russianRussian}}</option>
{{/select}}
</select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.ringingHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-bell-o"></i></div>
{{#unless extra.ringingTimeout}}
{{#compare extra.ringingTimeout "===" 0}}
<div class="ringing-timeout">
{{else}}
<div class="ringing-timeout disabled" data-original-title="{{i18n.users.editionForm.disabledTimeoutHint}}" data-placement="top" data-toggle="tooltip">
{{/compare}}
{{else}}
{{#if extra.groupTimeout}}
<div class="ringing-timeout disabled" data-original-title="{{i18n.users.editionForm.timeoutEditHint}}" data-placement="top" data-toggle="tooltip">
{{else}}
<div class="ringing-timeout">
{{/if}}
{{/unless}}
<label for="ringing_timeout">
{{ i18n.users.editionForm.timeout }}
</label>
<input id="ringing_timeout" class="input-mini" type="text" name="extra.ringingTimeout" value="{{extra.ringingTimeout}}"></input>
<span>{{ i18n.users.editionForm.timeoutSec }}</span>
</div>
{{#if extra.groupTimeout}}
<div class="timeout-edit">
<a id="open_fmfm_link" href="javascript:void(0);" class="monster-link blue">{{ i18n.users.editionForm.timeoutEdit }}</a>
</div>
{{/if}}
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-toggle="tooltip" title="{{i18n.users.editionForm.mainExtensionHelp}}" data-placement="top">
<i class="icon-telicon-extensions"></i>
</div>
<div class="inline-block">
<label for="presence_id">
{{ i18n.users.editionForm.presenceId }}
</label>
<select name="presence_id" id="presence_id" class="input-small">
{{#select presence_id}}
{{#each extra.presenceIdOptions}}
<option value="{{key}}">
{{value}}
</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-toggle="tooltip" title="{{i18n.commonMisc.outboundPrivacy.tooltip}}" data-placement="top">
<i class="fa fa-user-secret"></i>
</div>
<div class="inline-block">
<label for="outbound_privacy">
{{ i18n.commonMisc.outboundPrivacy.label }}
</label>
<select name="caller_id_options.outbound_privacy" id="outbound_privacy" class="input-medium">
{{#select caller_id_options.outbound_privacy}}
{{#each extra.outboundPrivacy}}
<option value="{{key}}">
{{value}}
</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
</div>
<!--<div class="advanced-fields content-centered span3">
<a id="resend_instructions" href="javascript:void(0)">{{ i18n.users.resendInstructions }}</a>
<button type="button" id="reset_password" class="monster-button monster-button-primary">{{ i18n.users.resetPassword }}</button>
</div>-->
</form>
<div class="actions">
<!-- We forbid users to delete their own user -->
{{#compare id "!==" extra.adminId}}
<a id="delete_user" class="monster-link" href="javascript:void(0);"><i class="fa fa-trash-o monster-red"></i>{{ i18n.users.delete }}</a>
{{/compare}}
{{#if extra.canImpersonate}}
<a id="impersonate_user" class="monster-link" href="javascript:void(0);"><i class="fa fa-user monster-blue"></i>{{ i18n.users.impersonate }}</a>
{{/if}}
<div class="pull-right">
<a class="cancel-link monster-link blue" href="javascript:void(0);">{{ i18n.cancel }}</a>
<button type="button" class="monster-button monster-button-success save-user">{{ i18n.saveChanges }}</button>
</div>
</div>
</div>
<div class="detail-user">
<form id="form-{{id}}" class="user-fields row-fluid">
<div class="basic-fields span6">
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.nameHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-user"></i></div>
<input required type="text" class="input-small" name="first_name" value="{{first_name}}" placeholder="{{i18n.users.editionForm.firstName}}"></input><input required class="input-small fix-left" type="text" name="last_name" value="{{last_name}}" placeholder="{{i18n.users.editionForm.lastName}}"></input>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.loginHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-key"></i></div>
<span id="username">{{username}}</span>
<a href="javascript:void(0);" id="change_username" class="monster-link blue">{{i18n.users.editionForm.changeUsername}}</a>
</div>
{{#if extra.vmbox.id}}
<div class="row-fields vmbox">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.vmboxHelp}}" data-placement="top" data-toggle="tooltip"><i class="icon-telicon-voicemail"></i></div>
<label for="vmbox_number">
{{ i18n.users.editionForm.vmboxNumber }}
</label>
{{extra.vmbox.mailbox}}
<a href="javascript:void(0);" id="change_pin" class="monster-link blue">{{i18n.users.editionForm.changePIN}}</a>
</div>
{{/if}}
<div class="email-border{{#if extra.differentEmail }} open{{/if}}">
<div class="row-fields email-checkbox">
<label class="fix-left">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendToDifferentEmail }}
<input id="notification_email" type="checkbox" name="extra.differentEmail"{{#if extra.differentEmail}} checked{{/if}}></input>
{{/monsterCheckbox}}
</label>
</div>
<div class="row-fields email-group">
<div class="fa fa-wrapper"><i class="fa fa-envelope"></i></div>
<input type="email" name="extra.email" id="email" placeholder="{{i18n.users.dialogCreationUser.notificationEmail}}"{{#if extra.differentEmail}} value="{{email}}"{{/if}}>
</div>
</div>
{{#if extra.mainCallflowId}}
<div class="row-fields">
<label class="fix-left">
{{#monsterCheckbox i18n.users.includeInDirectory }}
<input id="include_directory" type="checkbox" name="extra.includeInDirectory"{{#if extra.includeInDirectory}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</label>
</div>
{{/if}}
</div>
<div class="basic-fields span6">
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.roleHelp}}" data-placement="top" data-toggle="tooltip"><i class="icon-telicon-moderator"></i></div>
<select id="priv_level" name="priv_level">
{{#compare priv_level '===' 'admin'}}
<option value="admin" selected>{{ i18n.users.admin }}</option>
<option value="user">{{ i18n.users.user }}</option>
{{else}}
<option value="admin">{{ i18n.users.admin }}</option>
<option value="user" selected>{{ i18n.users.user }}</option>
{{/compare}}
</select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.timezoneHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-clock-o"></i></div>
<select id="user_timezone" name="timezone" data-original_value="{{timezone}}"></select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.languageHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-flag"></i></div>
<select id="user_language" name="extra.language">
{{#select language}}
<option value="auto">{{i18n.languages.auto}}</option>
<option value="en-US">{{i18n.languages.americanEnglish}}</option>
<option value="fr-FR">{{i18n.languages.frenchFrench}}</option>
<option value="de-DE">{{i18n.languages.germanGerman}}</option>
<option value="ru-RU">{{i18n.languages.russianRussian}}</option>
{{/select}}
</select>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-original-title="{{i18n.users.editionForm.ringingHelp}}" data-placement="top" data-toggle="tooltip"><i class="fa fa-bell-o"></i></div>
{{#unless extra.ringingTimeout}}
{{#compare extra.ringingTimeout "===" 0}}
<div class="ringing-timeout">
{{else}}
<div class="ringing-timeout disabled" data-original-title="{{i18n.users.editionForm.disabledTimeoutHint}}" data-placement="top" data-toggle="tooltip">
{{/compare}}
{{else}}
{{#if extra.groupTimeout}}
<div class="ringing-timeout disabled" data-original-title="{{i18n.users.editionForm.timeoutEditHint}}" data-placement="top" data-toggle="tooltip">
{{else}}
<div class="ringing-timeout">
{{/if}}
{{/unless}}
<label for="ringing_timeout">
{{ i18n.users.editionForm.timeout }}
</label>
<input id="ringing_timeout" class="input-mini" type="text" name="extra.ringingTimeout" value="{{extra.ringingTimeout}}"></input>
<span>{{ i18n.users.editionForm.timeoutSec }}</span>
</div>
{{#if extra.groupTimeout}}
<div class="timeout-edit">
<a id="open_fmfm_link" href="javascript:void(0);" class="monster-link blue">{{ i18n.users.editionForm.timeoutEdit }}</a>
</div>
{{/if}}
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-toggle="tooltip" title="{{i18n.users.editionForm.mainExtensionHelp}}" data-placement="top">
<i class="icon-telicon-extensions"></i>
</div>
<div class="inline-block">
<label for="presence_id">
{{ i18n.users.editionForm.presenceId }}
</label>
<select name="presence_id" id="presence_id" class="input-small">
{{#select presence_id}}
{{#each extra.presenceIdOptions}}
<option value="{{key}}">
{{value}}
</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="row-fields">
<div class="fa fa-wrapper" data-toggle="tooltip" title="{{i18n.commonMisc.outboundPrivacy.tooltip}}" data-placement="top">
<i class="fa fa-user-secret"></i>
</div>
<div class="inline-block">
<label for="outbound_privacy">
{{ i18n.commonMisc.outboundPrivacy.label }}
</label>
<select name="caller_id_options.outbound_privacy" id="outbound_privacy" class="input-medium">
{{#select caller_id_options.outbound_privacy}}
{{#each extra.outboundPrivacy}}
<option value="{{key}}">
{{value}}
</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
</div>
<!--<div class="advanced-fields content-centered span3">
<a id="resend_instructions" href="javascript:void(0)">{{ i18n.users.resendInstructions }}</a>
<button type="button" id="reset_password" class="monster-button monster-button-primary">{{ i18n.users.resetPassword }}</button>
</div>-->
</form>
<div class="actions">
<!-- We forbid users to delete their own user -->
{{#compare id "!==" extra.adminId}}
<a id="delete_user" class="monster-link" href="javascript:void(0);"><i class="fa fa-trash-o monster-red"></i>{{ i18n.users.delete }}</a>
{{/compare}}
{{#if extra.canImpersonate}}
<a id="impersonate_user" class="monster-link" href="javascript:void(0);"><i class="fa fa-user monster-blue"></i>{{ i18n.users.impersonate }}</a>
{{/if}}
<div class="pull-right">
<a class="cancel-link monster-link blue" href="javascript:void(0);">{{ i18n.cancel }}</a>
<button type="button" class="monster-button monster-button-success save-user">{{ i18n.saveChanges }}</button>
</div>
</div>
</div>

Loading…
Cancel
Save