Browse Source

UI-3301: Load main number's E911 dialog on top notification click at Smart PBX (#119)

* Change styles file extension from .css to .scss, to use SASS

* Set style for e911 information banner

* Show main company numbers section and E911 popup on banner click

* Refactor strategyRefreshTemplate to receive arguments via object wrapper

* Pass action from myOffice layout to strategy.strategyRefreshTemplate

* Check e911 number availability and show edit dialog or toast

* Fix typo in property name

* Extract main numbers refresh template process into separate function

* Refactor to use Lodash instead of jQuery utility functions

* Pass action to banner via topMessage object

* Refactor checkMissingE911 function

* Convert action to object, to include more parameters

* Add action callback for e911 at numbers strategy

* Fixes on flow to check if update Emergency Caller ID

* Request current account from API, when checking to update E911 caller ID

* Fix e911 phone number filter

* Use current authenticated account instead of requesting it from API
4.3
Guillermo Gutiérrez 7 years ago
committed by GitHub
parent
commit
2f10b6c8b7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 436 additions and 139 deletions
  1. +2
    -1
      i18n/en-US.json
  2. +13
    -4
      submodules/myOffice/myOffice.js
  3. +26
    -9
      submodules/myOffice/myOffice.scss
  4. +2
    -2
      submodules/myOffice/views/layout.html
  5. +393
    -123
      submodules/strategy/strategy.js

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

@ -958,7 +958,8 @@
"resetCallSuccess": "Call strategy successfully reset.",
"buyNumbersSuccess": "The numbers have been purchased.",
"buyNumbersError": "An error occured while purchasing numbers.",
"removeNumberSuccess": "Number successfully removed from the main callflow."
"removeNumberSuccess": "Number successfully removed from the main callflow.",
"noE911NumberAvailable": "There are no main numbers with e911 feature available. Please assign a main number with the e911 feature."
},
"popupRemoveFeatures": {
"title": "Features",


+ 13
- 4
submodules/myOffice/myOffice.js View File

@ -661,7 +661,8 @@ define(function(require) {
if (!hasValidCallerId && !hasValidE911) {
data.topMessage = {
cssClass: 'btn-danger',
message: self.i18n.active().myOffice.missingCnamE911Message
message: self.i18n.active().myOffice.missingCnamE911Message,
action: 'checkMissingE911'
};
} else if (!hasValidCallerId) {
data.topMessage = {
@ -671,7 +672,8 @@ define(function(require) {
} else if (!hasValidE911) {
data.topMessage = {
cssClass: 'btn-danger',
message: self.i18n.active().myOffice.missingE911Message
message: self.i18n.active().myOffice.missingE911Message,
action: 'checkMissingE911'
};
}
}
@ -700,7 +702,8 @@ define(function(require) {
template.find('.link-box').on('click', function(e) {
var $this = $(this),
category = $this.data('category'),
subcategory = $this.data('subcategory');
subcategory = $this.data('subcategory'),
actionType = $this.data('action');
$('.category').removeClass('active');
switch (category) {
@ -718,7 +721,13 @@ define(function(require) {
break;
case 'strategy':
$('.category#strategy').addClass('active');
monster.pub('voip.strategy.render', { parent: parent, openElement: subcategory });
monster.pub('voip.strategy.render', {
parent: parent,
openElement: subcategory,
action: {
type: actionType
}
});
break;
}
});


submodules/myOffice/myOffice.css → submodules/myOffice/myOffice.scss View File


+ 2
- 2
submodules/myOffice/views/layout.html View File

@ -23,7 +23,7 @@
<div class="dashboard-content">
{{#if topMessage}}
<div class="dashboard-top-message {{topMessage.cssClass}}">
<div class="dashboard-top-message {{topMessage.cssClass}} link-box" data-category="strategy" data-subcategory="main-number"{{#if topMessage.action}} data-action="{{topMessage.action}}"{{/if}}>
<i class="fa fa-exclamation-triangle"></i>{{topMessage.message}}<!-- {{i18n.myOffice.missingE911Message}} -->
</div>
{{/if}}
@ -123,7 +123,7 @@
{{else}}
<div class="dashboard-warning-row main-number-row link-box" data-category="strategy" data-subcategory="main-number">
<i class="fa fa-exclamation-triangle"></i>{{@root.i18n.myOffice.missingMainNumberMessage}}
</div>
</div>
{{/if}}
<div class="dashboard-box-row conf-number-row link-box" data-category="strategy" data-subcategory="strategy-confnum">
<div class="row-title">


+ 393
- 123
submodules/strategy/strategy.js View File

@ -158,11 +158,23 @@ define(function(require) {
}
],
/**
* Render main layout
* @param {Object} args
* @param {jQuery} args.parent Parent template
* @param {String} [args.openElement] Name of the element to display on render
* @param {Object} [args.action] Action to execute on render
* @param {String} [args.action.type] Type of action to execute
* @param {Object} [args.action.callbacks] Action callbacks
* @param {Function} [args.action.callbacks.success] Action success callback
* @param {Function} [args.callback] Callback to execute after render
*/
strategyRender: function(args) {
var self = this,
args = args || {},
parent = args.parent || $('.right-content'),
openElement = args.openElement,
action = args.action,
callback = args.callback;
monster.parallel({
@ -232,9 +244,14 @@ define(function(require) {
if (openElement) {
var element = template.find('.element-container.' + openElement + ':visible');
if (element.length > 0) {
self.strategyRefreshTemplate(element, results, function() {
element.addClass('open');
element.find('.element-content').show();
self.strategyRefreshTemplate({
container: element,
strategyData: results,
action: action,
callback: function() {
element.addClass('open');
element.find('.element-content').show();
}
});
}
}
@ -337,13 +354,23 @@ define(function(require) {
});
},
strategyShowE911Choices: function(oldNumber, newNumbers) {
/**
* Show e911 number choices
* @param {Object} args
* @param {String} args.oldNumber Old E911 number
* @param {String[]} args.newNumbers New E911 numbers
* @param {Function} [args.success] Success callback
* @param {Function} [args.error] Error callback
* @param {Function} [args.cancel] Cancel callback
*/
strategyShowE911Choices: function(args) {
var self = this,
oldNumber = args.oldNumber,
template = $(self.getTemplate({
name: 'changeE911Popup',
data: {
oldNumber: oldNumber,
newNumbers: newNumbers
newNumbers: args.newNumbers
},
submodule: 'strategy'
})),
@ -361,23 +388,33 @@ define(function(require) {
if (number === oldNumber) {
popup.dialog('close');
} else {
self.strategyChangeEmergencyCallerId(number, function() {
monster.ui.toast({
type: 'success',
message: self.getTemplate({
name: '!' + self.i18n.active().startegy.updateE911Dialog.success,
data: {
number: monster.util.formatPhoneNumber(number)
}
})
});
popup.dialog('close');
self.strategyChangeEmergencyCallerId({
number: number,
success: function(number) {
monster.ui.toast({
type: 'success',
message: self.getTemplate({
name: '!' + self.i18n.active().strategy.updateE911Dialog.success,
data: {
number: monster.util.formatPhoneNumber(number)
}
})
});
popup.dialog('close');
_.has(args, 'success') && args.success(number);
},
error: function(parsedError) {
_.has(args, 'error') && args.error(parsedError);
}
});
}
});
template.find('.cancel-link').on('click', function() {
popup.dialog('close');
_.has(args, 'cancel') && args.cancel();
});
var popup = monster.ui.dialog(template, {
@ -411,9 +448,13 @@ define(function(require) {
}
});
self.strategyRefreshTemplate(element, strategyData, function() {
element.addClass('open');
element.find('.element-content').slideDown();
self.strategyRefreshTemplate({
container: element,
strategyData: strategyData,
callback: function() {
element.addClass('open');
element.find('.element-content').slideDown();
}
});
}
});
@ -434,105 +475,127 @@ define(function(require) {
self.strategyCallsBindEvents(strategyCallsContainer, strategyData);
},
strategyCheckIfUpdateEmergencyCallerID: function(templateNumbers, features, number) {
var self = this;
/**
*
* @param {Object} args
* @param {Object[]} args.templateNumbers Template numbers
* @param {String[]} args.features Number features
* @param {String} args.number Number ID
* @param {Object} [args.callbacks] Function callbacks
* @param {Function} [args.callbacks.success] Successs callback
*/
strategyCheckIfUpdateEmergencyCallerID: function(args) {
var self = this,
templateNumbers = args.templateNumbers,
features = args.features,
number = args.number;
// Update data we have about features for main numbers
_.each(templateNumbers, function(dataLoop, index) {
_.each(templateNumbers, function(dataLoop) {
if (dataLoop.number.id === number) {
dataLoop.number.features = features;
}
});
var e911Numbers = _.map(_.filter(templateNumbers, function(number) {
return (number.number.features || []).indexOf('e911') >= 0;
}), function(number) {
return number.number.id;
});
monster.waterfall([
function(callback) {
var currAcc = monster.apps.auth.currentAccount,
hasEmergencyCallerId = _.get(currAcc, 'caller_id.emergency.number', '') !== '',
hasE911Feature = _.includes(features || [], 'e911');
if (hasE911Feature && !hasEmergencyCallerId) {
self.strategyChangeEmergencyCallerId({
number: number,
success: function() {
callback({});
},
error: function(parsedError) {
callback(_.merge({
isError: true
}, parsedError));
}
});
} else {
callback(null, currAcc, hasEmergencyCallerId, hasE911Feature);
}
},
function(currAcc, hasEmergencyCallerId, hasE911Feature, callback) {
if (!hasEmergencyCallerId) {
callback({});
return;
}
var e911ChoicesArgs;
var currAcc = monster.apps.auth.currentAccount,
hasEmergencyCallerId = currAcc.hasOwnProperty('caller_id') && currAcc.caller_id.hasOwnProperty('emergency') && currAcc.caller_id.emergency.hasOwnProperty('number') && currAcc.caller_id.emergency.number !== '',
hasE911Feature = (features || []).indexOf('e911') >= 0;
if (hasE911Feature) {
if (currAcc.caller_id.emergency.number !== number) {
e911ChoicesArgs = {
oldNumber: currAcc.caller_id.emergency.number,
newNumbers: [ number ]
};
}
} else {
if (currAcc.caller_id.emergency.number === number) {
e911ChoicesArgs = {
newNumbers: _.chain(templateNumbers)
.filter(function(number) {
return _.includes(number.number.features || [], 'e911');
}).map(function(number) {
return number.number.id;
}).value()
};
}
}
if (hasE911Feature) {
if (hasEmergencyCallerId) {
if (currAcc.caller_id.emergency.number !== number) {
self.strategyShowE911Choices(currAcc.caller_id.emergency.number, [ number ]);
if (_.isUndefined(e911ChoicesArgs)) {
callback({});
} else {
callback(null, e911ChoicesArgs);
}
} else {
self.strategyChangeEmergencyCallerId(number);
},
function(e911ChoicesArgs, callback) {
var executeCallback = function() {
callback();
};
self.strategyShowE911Choices(_.merge({
success: executeCallback,
cancel: executeCallback
}, e911ChoicesArgs));
}
} else {
// If they removed e911 from their current emergency caller id number, then we let them select the new one from the list of numbers with e911 configured
if (hasEmergencyCallerId && currAcc.caller_id.emergency.number === number) {
self.strategyShowE911Choices(undefined, e911Numbers);
], function(err) {
if (!err || _.isEmpty(err)) {
_.has(args, 'callbacks.success') && args.callbacks.success();
}
}
});
},
strategyRefreshTemplate: function(container, strategyData, callback) {
/**
* Refresh a strategy template
* @param {Object} args
* @param {jQuery} args.container Container template
* @param {Object} args.strategyData Strategy data
* @param {Object} [args.action] Action to execute
* @param {String} [args.action.type] Type of action to execute
* @param {Object} [args.action.callbacks] Action callbacks
* @param {Function} [args.action.callbacks.success] Action success callback
* @param {Function} [args.callback] Optional callback to execute after refresh
*/
strategyRefreshTemplate: function(args) {
var self = this,
templateName = container.data('template');
$container = args.container,
strategyData = args.strategyData,
callback = args.callback,
templateName = $container.data('template');
switch (templateName) {
case 'numbers':
self.strategyListAccountNumbers(function(accountNumbers) {
var callflow = strategyData.callflows.MainCallflow,
numbers = callflow.numbers,
templateData = {
hideBuyNumbers: monster.config.whitelabel.hasOwnProperty('hideBuyNumbers')
? monster.config.whitelabel.hideBuyNumbers
: false,
numbers: $.map(numbers, function(val, key) {
if (val !== '0' && val !== 'undefinedMainNumber') {
var ret = {
number: {
id: val
}
};
if (accountNumbers.hasOwnProperty(val)) {
ret.number = $.extend(true, accountNumbers[val], ret.number);
}
return ret;
}
}),
spareLinkEnabled: (_.countBy(accountNumbers, function(number) { return number.used_by ? 'assigned' : 'spare'; }).spare > 0)
},
template = $(self.getTemplate({
name: 'strategy-' + templateName,
data: templateData,
submodule: 'strategy'
}));
_.each(templateData.numbers, function(data) {
data.number.phoneNumber = data.number.id;
var numberDiv = template.find('[data-phonenumber="' + data.number.id + '"]'),
args = {
target: numberDiv.find('.edit-features'),
numberData: data.number,
afterUpdate: function(features) {
monster.ui.paintNumberFeaturesIcon(features, numberDiv.find('.features'));
self.strategyCheckIfUpdateEmergencyCallerID(templateData.numbers, features, data.number.id);
}
};
monster.pub('common.numberFeaturesMenu.render', args);
});
monster.ui.tooltips(template);
container
.find('.element-content')
.empty()
.append(template);
callback && callback();
});
self.strategyRefreshTemplateNumbers(
_.merge(
{
templateName: templateName
}, args)
);
break;
case 'confnum':
self.strategyListAccountNumbers(function(accountNumbers) {
@ -557,7 +620,7 @@ define(function(require) {
submodule: 'strategy'
}));
container
$container
.find('.element-content')
.empty()
.append(template);
@ -590,7 +653,7 @@ define(function(require) {
submodule: 'strategy'
}));
container
$container
.find('.element-content')
.empty()
.append(template);
@ -697,7 +760,7 @@ define(function(require) {
monster.ui.validate(template.find('#strategy_custom_hours_form'), validationOptions);
container
$container
.find('.element-content')
.empty()
.append(template);
@ -718,7 +781,7 @@ define(function(require) {
})),
holidayList = template.find('.holidays-list');
container
$container
.find('.element-content')
.empty()
.append(template);
@ -786,7 +849,7 @@ define(function(require) {
submodule: 'strategy'
}));
container
$container
.find('.element-content')
.empty()
.append(template);
@ -863,19 +926,190 @@ define(function(require) {
}
},
strategyChangeEmergencyCallerId: function(number, callback) {
var self = this;
/**
* Refresh main numbers strategy template
* @param {Object} args
* @param {jQuery} args.container Container template
* @param {Object} args.strategyData Strategy data
* @param {Object} args.templateName Template name
* @param {Object} [args.action] Action to execute
* @param {String} [args.action.type] Type of action to execute
* @param {Object} [args.action.callbacks] Action callbacks
* @param {Function} [args.action.callbacks.success] Action success callback
* @param {Function} [args.callback] Optional callback to execute after refresh
*/
strategyRefreshTemplateNumbers: function(args) {
var self = this,
$container = args.container,
strategyData = args.strategyData,
templateName = args.templateName,
action = args.action,
actionType = _.get(action, 'type'),
callback = args.callback;
self.strategyGetAccount({
success: function(data) {
self.strategyListAccountNumbers(function(accountNumbers) {
var callflow = strategyData.callflows.MainCallflow,
numbers = callflow.numbers,
templateData = {
hideBuyNumbers: _.has(monster.config.whitelabel, 'hideBuyNumbers')
? monster.config.whitelabel.hideBuyNumbers
: false,
numbers: _.chain(numbers)
.filter(function(val) {
return val !== '0' && val !== 'undefinedMainNumber';
}).map(function(val) {
var ret = {
number: {
id: val
}
};
if (_.has(accountNumbers, val)) {
ret.number = _.merge(accountNumbers[val], ret.number);
}
return ret;
}).value(),
spareLinkEnabled: (_.countBy(accountNumbers, function(number) { return number.used_by ? 'assigned' : 'spare'; }).spare > 0)
},
template = $(self.getTemplate({
name: 'strategy-' + templateName,
data: templateData,
submodule: 'strategy'
})),
afterFeatureUpdate = function afterFeatureUpdate(numberId, numberFeatures, callbacks) {
monster.ui.paintNumberFeaturesIcon(numberFeatures, template.find('[data-phonenumber="' + numberId + '"] .features'));
self.strategyCheckIfUpdateEmergencyCallerID({
templateNumbers: templateData.numbers,
features: numberFeatures,
number: numberId,
callbacks: callbacks
});
},
actions = {
checkMissingE911: function(numbers) {
if (!monster.util.isNumberFeatureEnabled('e911')) {
// E911 feature is not enabled for the account, so there is
// nothing to do here
return;
}
// Cases:
// - Render popup for first number in list with E911 feature enabled,
// if no number has this feature set
// - If no number has E911 feature enabled, show a warning toast to
// inform the user of that fact
var isE911Active = _.some(numbers, function(numberData) {
return _.includes(numberData.number.features, 'e911');
}),
e911NumberData;
if (!isE911Active) {
e911NumberData = _.chain(numbers)
.find(function(numberData) {
var availableFeatures = monster.util.getNumberFeatures(numberData.number);
return _.includes(availableFeatures, 'e911');
}).get('number').value();
if (_.isUndefined(e911NumberData)) {
monster.ui.toast({
type: 'warning',
message: self.i18n.active().strategy.toastrMessages.noE911NumberAvailable
});
} else {
monster.pub('common.e911.renderPopup', {
phoneNumber: _.get(e911NumberData, 'phoneNumber', e911NumberData.id),
callbacks: {
success: function(data) {
afterFeatureUpdate(e911NumberData.id, data.data.features, action.callbacks);
},
error: function() {
_.has(action, 'callbacks.error')
&& action.callbacks.error();
}
}
});
}
}
}
};
_.each(templateData.numbers, function(data) {
data.number.phoneNumber = data.number.id;
var numberData = data.number,
args = {
target: template.find('[data-phonenumber="' + numberData.id + '"] .edit-features'),
numberData: numberData,
afterUpdate: function(features) {
afterFeatureUpdate(numberData.id, features);
}
};
monster.pub('common.numberFeaturesMenu.render', args);
});
monster.ui.tooltips(template);
$container
.find('.element-content')
.empty()
.append(template);
// Execute any additional action that has been requested
if (_.has(actions, actionType)) {
actions[actionType](templateData.numbers);
}
callback && callback();
});
},
/**
* Change the emergency caller ID for the current account
* @param {Object} args
* @param {String} args.number Number to be set for emergency calls
* @param {Function} [args.success] Success callback
* @param {Function} [args.error] Error callback
*/
strategyChangeEmergencyCallerId: function(args) {
var self = this,
number = args.number;
monster.waterfall([
function(callback) {
self.strategyGetAccount({
success: function(data) {
callback(null, data);
},
error: function(parsedError) {
callback(parsedError);
}
});
},
function(data, callback) {
data.caller_id = data.caller_id || {};
data.caller_id.emergency = data.caller_id.emergency || {};
data.caller_id.emergency.number = number;
self.strategyUpdateAccount(data, function() {
callback && callback(number);
self.strategyUpdateAccount({
data: data,
success: function() {
callback(null);
},
error: function(parsedError) {
callback(parsedError);
}
});
}
], function(err) {
if (err) {
_.has(args, 'error') && args.error(err);
} else {
_.has(args, 'success') && args.success(number);
}
});
},
@ -896,7 +1130,10 @@ define(function(require) {
var parentContainer = container.parents('.element-container');
strategyData.callflows.MainCallflow = updatedCallflow;
refreshNumbersHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
});
}
},
@ -1001,7 +1238,10 @@ define(function(require) {
});
strategyData.callflows.MainCallflow = updatedCallflow;
refreshNumbersHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
//Updating Company Caller ID if this was the selected number
self.callApi({
@ -1095,7 +1335,10 @@ define(function(require) {
var parentContainer = container.parents('.element-container');
strategyData.callflows.MainConference = updatedCallflow;
refreshConfNumHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
});
}
},
@ -1276,7 +1519,10 @@ define(function(require) {
});
strategyData.callflows.MainConference = updatedCallflow;
refreshConfNumHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
});
}
});
@ -1327,7 +1573,10 @@ define(function(require) {
var parentContainer = container.parents('.element-container');
strategyData.callflows.MainFaxing = updatedCallflow;
refreshFaxingNumHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
});
};
@ -1561,7 +1810,10 @@ define(function(require) {
});
strategyData.callflows.MainFaxing = updatedCallflow;
refreshFaxingNumHeader(parentContainer);
self.strategyRefreshTemplate(parentContainer, strategyData);
self.strategyRefreshTemplate({
container: parentContainer,
strategyData: strategyData
});
});
}
});
@ -3861,8 +4113,17 @@ define(function(require) {
});
},
strategyUpdateAccount: function(data, callback) {
var self = this;
/**
* Updates an account
* @param {Object} args
* @param {Object} args.data Account data
* @param {String} args.data.id Account ID
* @param {Function} [args.success] Success callback
* @param {Function} [args.error] Error callback
*/
strategyUpdateAccount: function(args) {
var self = this,
data = args.data;
self.callApi({
resource: 'account.update',
@ -3870,12 +4131,21 @@ define(function(require) {
accountId: data.id,
data: data
},
success: function(data, status) {
callback && callback(data.data);
success: function(data) {
_.has(args, 'success') && args.success(data.data);
},
error: function(parsedError) {
_.has(args, 'error') && args.error(parsedError);
}
});
},
/**
* Request the current account information
* @param {Object} args
* @param {Function} [args.success] Success callback
* @param {Fucntion} [args.error] Error callback
*/
strategyGetAccount: function(args) {
var self = this;
@ -3885,10 +4155,10 @@ define(function(require) {
accountId: self.accountId
},
success: function(data, status) {
args.hasOwnProperty('success') && args.success(data.data);
_.has(args, 'success') && args.success(data.data);
},
error: function(data, status) {
args.hasOwnProperty('error') && args.error();
_.has(args, 'error') && args.error();
}
});
},


Loading…
Cancel
Save