Browse Source

MSPB-131: Update linked callflow on mobile device un\assignment (#256)

* Refactor user's device assignment logic

* Extract device assignment logic into app methods

* Move device retrieval into control flow

* Move un\assign logic into control flow

* Extract mobile callflow update into own function

* Patch device owner_id instead of full update

* Move mobile callflow id retrieval to control flow

* Patch callflow to avoid having to retrieve it first

* Consolidate un\assignment logic

* Migrate single-used app methods into closure

* Extract mobile callflow update logic into app method

* Run device patch and mobile callflow update in parallel

* Move back device assignment logic to users module

* Consolidate mobile callflow update w/ user callfow fetching logic

* Update linked callflow on mobile device un\assignment

* Refactor entities update logic on device un\assignment
4.3
Joris Tirado 5 years ago
committed by GitHub
parent
commit
83fa9c3529
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 285 additions and 168 deletions
  1. +121
    -0
      app.js
  2. +35
    -8
      submodules/devices/devices.js
  3. +129
    -160
      submodules/users/users.js

+ 121
- 0
app.js View File

@ -194,6 +194,127 @@ define(function(require) {
.find('.grid-cell.active, .grid-row.active')
.removeClass('active');
});
},
patchCallflow: function(args) {
var self = this;
self.callApi({
resource: 'callflow.patch',
data: _.merge({
accountId: self.accountId
}, args.data),
success: function(data) {
_.has(args, 'success') && args.success(data.data);
},
error: function(parsedError) {
_.has(args, 'error') && args.error(parsedError);
}
});
},
/**
* Runs tasks necessary for mobile device callfow update on un\assignement.
* @param {String|null} userId
* @param {String|null|undefined} userMainCallflowId
* @param {Object} device
* @param {String} device.id
* @param {String} device.mobile.mdn
* @param {Function} mainCallback
*
* updateMobileCallflowAssignment(userId, userMainCallflowId|undefined, ...)
* this signature will assign the device
*
* updateMobileCallflowAssignment(null, null, ...)
* this signature will unassign the device
*
* While assigning, you can either provide the user's main callflow's ID or set it to
* `undefined`, in which case the method will take care of resolving it based on `userId`.
*/
updateMobileCallflowAssignment: function(userId, userMainCallflowId, device, mainCallback) {
var self = this,
getMainUserCallflowId = function getMainUserCallflowId(userId, callback) {
self.callApi({
resource: 'callflow.list',
data: {
accountId: self.accountId,
filters: {
filter_owner_id: userId,
filter_type: 'mainUserCallflow'
}
},
success: _.flow(
_.partial(_.get, _, 'data'),
_.head,
_.partial(_.get, _, 'id'),
_.partial(callback, null)
),
error: _.partial(callback, true)
});
},
maybeGetMainUserCallflowId = function maybeGetMainUserCallflowId(userId, userMainCallflowId, callback) {
if (_.isNull(userMainCallflowId) || !_.isUndefined(userMainCallflowId)) {
return callback(null, userMainCallflowId);
}
getMainUserCallflowId(userId, callback);
},
getMobileCallflowIdByNumber = function getMobileCallflowIdByNumber(number, callback) {
self.callApi({
resource: 'callflow.searchByNumber',
data: {
accountId: self.accountId,
value: number
},
success: _.flow(
_.partial(_.get, _, 'data'),
_.head,
_.partial(_.get, _, 'id'),
_.partial(callback, null)
),
error: _.partial(callback, true)
});
},
getCallflowIds = function getCallflowIds(userId, userMainCallflowId, number, callback) {
monster.parallel({
userMainCallflowId: _.partial(maybeGetMainUserCallflowId, userId, userMainCallflowId),
mobileCallflowId: _.partial(getMobileCallflowIdByNumber, number)
}, callback);
},
updateMobileCallflowAssignment = function updateMobileCallflowAssignment(userId, deviceId, callflowIds, callback) {
var userMainCallflowId = callflowIds.userMainCallflowId,
mobileCallflowId = callflowIds.mobileCallflowId,
updatedCallflow = _.merge({
owner_id: userId
}, _.isNull(userMainCallflowId) ? {
flow: {
module: 'device',
data: {
id: deviceId
}
}
} : {
flow: {
module: 'callflow',
data: {
id: userMainCallflowId
}
}
});
self.patchCallflow({
data: {
callflowId: mobileCallflowId,
data: updatedCallflow
},
success: _.partial(callback, null),
error: _.partial(callback, true)
});
};
monster.waterfall([
_.partial(getCallflowIds, userId, userMainCallflowId, device.mobile.mdn),
_.partial(updateMobileCallflowAssignment, userId, device.id)
], mainCallback);
}
};


+ 35
- 8
submodules/devices/devices.js View File

@ -591,7 +591,7 @@ define(function(require) {
$this.prop('disabled', 'disabled');
self.devicesSaveDevice(dataToSave, function(data) {
self.devicesSaveDevice(data.owner_id, dataToSave, function(data) {
if (hasToRestart) {
self.devicesRestart(data.id, function() {
monster.ui.toast({
@ -1451,18 +1451,45 @@ define(function(require) {
},
/**
* @param {String|undefined} originalUserId
* @param {Object} deviceData
* @param {Function} callbackSuccess
* @param {Function} [callbackError]
*/
devicesSaveDevice: function(deviceData, callbackSuccess, callbackError) {
var self = this;
devicesSaveDevice: function(originalUserId, deviceData, callbackSuccess, callbackError) {
var self = this,
isMobileDevice = deviceData.device_type === 'mobile',
hasDifferentUserId = originalUserId !== deviceData.owner_id,
shouldUpdateMobileCallflow = isMobileDevice && hasDifferentUserId,
maybeUpdateMobileCallflowAssignment = function maybeUpdateMobileCallflowAssignment(shouldUpdateMobileCallflow, device, callback) {
if (!shouldUpdateMobileCallflow) {
return callback(null);
}
var userId = _.get(device, 'owner_id', null),
userMainCallflowId = userId ? undefined : null;
if (deviceData.id) {
self.devicesUpdateDevice(deviceData, callbackSuccess, callbackError);
} else {
self.devicesCreateDevice(deviceData, callbackSuccess, callbackError);
}
self.updateMobileCallflowAssignment(userId, userMainCallflowId, device, callback);
},
saveDevice = function saveDevice(device, callback) {
var method = _.has(device, 'id') ? 'devicesUpdateDevice' : 'devicesCreateDevice';
self[method](device, _.partial(callback, null), callback);
};
/**
* We perform both operations in parallel because, although app#updateMobileCallflowAssignment
* requires an existing device to run, since it is not possible to create mobile devices
* from smartpbx, that ID will always be present.
*/
monster.parallel({
_: _.partial(maybeUpdateMobileCallflowAssignment, shouldUpdateMobileCallflow, deviceData),
device: _.partial(saveDevice, deviceData)
}, function(err, results) {
if (err) {
return callbackError && callbackError(err);
}
callbackSuccess && callbackSuccess(results.device);
});
},
/**


+ 129
- 160
submodules/users/users.js View File

@ -4728,37 +4728,6 @@ define(function(require) {
});
},
usersGetDevice: function(deviceId, callback) {
var self = this;
self.callApi({
resource: 'device.get',
data: {
accountId: self.accountId,
deviceId: deviceId
},
success: function(data) {
callback(data.data);
}
});
},
usersUpdateDevice: function(data, callback) {
var self = this;
self.callApi({
resource: 'device.update',
data: {
accountId: self.accountId,
data: data,
deviceId: data.id
},
success: function(data) {
callback(data.data);
}
});
},
usersListDeviceUser: function(userId, callback) {
var self = this;
@ -4838,146 +4807,146 @@ define(function(require) {
});
},
usersUpdateDevices: function(data, userId, callbackAfterUpdate) {
/**
* @param {Object} data
* @param {String[]} data.newDevices List of device IDs to assign
* @param {String[]} data.oldDevices List of device IDs to unassign
* @param {String} userId
* @param {Function} callback
*/
usersUpdateDevices: function(data, userId, callback) {
var self = this,
updateDevices = function(userCallflow) {
var listFnParallel = [],
updateDeviceRequest = function(newDataDevice, callback) {
self.usersUpdateDevice(newDataDevice, function(updatedDataDevice) {
callback(null, updatedDataDevice);
});
};
_.each(data.newDevices, function(deviceId) {
listFnParallel.push(function(callback) {
self.usersGetDevice(deviceId, function(data) {
data.owner_id = userId;
if (data.device_type === 'mobile') {
self.usersSearchMobileCallflowsByNumber(userId, data.mobile.mdn, function(listCallflowData) {
self.callApi({
resource: 'callflow.get',
data: {
accountId: self.accountId,
callflowId: listCallflowData.id
},
success: function(rawCallflowData, status) {
var callflowData = rawCallflowData.data;
if (userCallflow) {
$.extend(true, callflowData, {
owner_id: userId,
flow: {
module: 'callflow',
data: {
id: userCallflow.id
}
}
});
} else {
$.extend(true, callflowData, {
owner_id: userId,
flow: {
module: 'device',
data: {
id: deviceId
}
}
});
}
self.usersUpdateCallflow(callflowData, function() {
updateDeviceRequest(data, callback);
});
}
});
});
} else {
updateDeviceRequest(data, callback);
getUserMainCallflow = function getUserMainCallflow(userId, next) {
self.usersGetMainCallflow(userId, _.partial(next, null));
},
assignDeviceFactory = function assignDeviceFactory(userId, userMainCallflowId, deviceId) {
return _.bind(self.usersUpdateDeviceAssignmentFromUser, self, deviceId, userId, userMainCallflowId);
},
unassignDeviceFactory = function unassignDeviceFactory(deviceId) {
return _.bind(self.usersUpdateDeviceAssignmentFromUser, self, deviceId, null, null);
},
updateCallflowEndpoints = function updateCallflowEndpoints(updatedEndpoints, callflowId, next) {
self.patchCallflow({
data: {
callflowId: callflowId,
data: {
flow: _.isEmpty(updatedEndpoints) ? {
module: 'user',
data: {
can_call_self: false,
endpoints: null,
id: userId,
timeout: 20
}
} : {
data: {
endpoints: updatedEndpoints
}
}
});
});
}
},
success: _.partial(next, null),
error: _.partial(next, true)
});
_.each(data.oldDevices, function(deviceId) {
listFnParallel.push(function(callback) {
self.usersGetDevice(deviceId, function(data) {
delete data.owner_id;
if (data.device_type === 'mobile') {
self.usersSearchMobileCallflowsByNumber(userId, data.mobile.mdn, function(listCallflowData) {
self.callApi({
resource: 'callflow.get',
data: {
accountId: self.accountId,
callflowId: listCallflowData.id
},
success: function(rawCallflowData, status) {
var callflowData = rawCallflowData.data;
delete callflowData.owner_id;
$.extend(true, callflowData, {
flow: {
module: 'device',
data: {
id: deviceId
}
}
});
self.usersUpdateCallflow(callflowData, function() {
updateDeviceRequest(data, callback);
});
}
});
});
} else {
updateDeviceRequest(data, callback);
},
disableFindMeFollowMeForUserId = function disableFindMeFollowMeForUserId(userId, next) {
self.usersPatchUser({
data: {
userId: userId,
data: {
smartpbx: {
find_me_follow_me: false
}
});
});
}
},
success: _.partial(next, null),
error: _.partial(next, true)
});
},
maybeUpdateUserAndCallflow = function maybeUpdateUserAndCallflow(userId, userCallflow, deviceIdsRemoved, next) {
var hasRingGroupModule = _.get(userCallflow, 'flow.module') === 'ring_group',
currentEndpoints = _.get(userCallflow, 'flow.data.endpoints', []),
updatedEndpoints = _.reject(currentEndpoints, _.flow(
_.partial(_.get, _, 'id'),
_.partial(_.includes, deviceIdsRemoved)
)),
whereEndpointsRemoved = _.size(updatedEndpoints) < _.size(currentEndpoints);
monster.parallel(_.flatten([
hasRingGroupModule && whereEndpointsRemoved ? [
_.partial(updateCallflowEndpoints, updatedEndpoints, userCallflow.id)
] : [],
hasRingGroupModule && whereEndpointsRemoved && _.isEmpty(updatedEndpoints) ? [
_.partial(disableFindMeFollowMeForUserId, userId)
] : []
]), next);
},
updateEntities = function updateEntities(userId, devices, userMainCallflow, next) {
monster.parallel(_.flatten([
_.map(data.newDevices, _.partial(assignDeviceFactory, userId, _.get(userMainCallflow, 'id'))),
_.map(data.oldDevices, unassignDeviceFactory),
_.partial(maybeUpdateUserAndCallflow, userId, userMainCallflow, data.oldDevices)
]), next);
};
if (data.oldDevices.length > 0 && userCallflow && userCallflow.flow.module === 'ring_group') {
var endpointsCount = userCallflow.flow.data.endpoints.length;
userCallflow.flow.data.endpoints = _.filter(userCallflow.flow.data.endpoints, function(endpoint) {
return (data.oldDevices.indexOf(endpoint.id) < 0);
});
monster.waterfall([
_.partial(getUserMainCallflow, userId),
_.partial(updateEntities, userId, data)
], callback);
},
if (userCallflow.flow.data.endpoints.length < endpointsCount) {
if (userCallflow.flow.data.endpoints.length === 0) {
userCallflow.flow.module = 'user';
userCallflow.flow.data = {
can_call_self: false,
id: userId,
timeout: '20'
};
listFnParallel.push(function(callback) {
self.usersGetUser(userId, function(user) {
user.smartpbx.find_me_follow_me.enabled = false;
self.usersUpdateUser(user, function(data) {
callback(null, data);
});
});
});
}
listFnParallel.push(function(callback) {
self.usersUpdateCallflow(userCallflow, function(data) {
callback(null, data);
});
});
}
usersUpdateDeviceAssignmentFromUser: function(deviceId, userId, userMainCallflowId, mainCallback) {
var self = this,
getDevice = function getDevice(deviceId, callback) {
self.callApi({
resource: 'device.get',
data: {
accountId: self.accountId,
deviceId: deviceId
},
success: _.flow(
_.partial(_.get, _, 'data'),
_.partial(callback, null)
),
error: _.partial(callback, true)
});
},
patchDevice = function patchDevice(data, deviceId, callback) {
self.callApi({
resource: 'device.patch',
data: {
accountId: self.accountId,
deviceId: deviceId,
data: data
},
success: _.flow(
_.partial(_.get, _, 'data'),
_.partial(callback, null)
),
error: _.partial(callback, true)
});
},
maybeUpdateMobileCallflowAssignment = function maybeUpdateMobileCallflowAssignment(userId, userMainCallflowId, device, callback) {
if (device.device_type !== 'mobile') {
return callback(null);
}
self.updateMobileCallflowAssignment(userId, userMainCallflowId, device, callback);
},
updateDeviceAssignment = function updateDeviceAssignment(userId, userMainCallflowId, device, callback) {
var updatedDevice = {
owner_id: userId
};
monster.parallel(listFnParallel, function(err, results) {
callbackAfterUpdate && callbackAfterUpdate(results);
});
monster.parallel([
_.partial(maybeUpdateMobileCallflowAssignment, userId, userMainCallflowId, device),
_.partial(patchDevice, updatedDevice, device.id)
], callback);
};
self.usersGetMainCallflow(userId, function(callflow) {
updateDevices(callflow);
});
monster.waterfall([
_.partial(getDevice, deviceId),
_.partial(updateDeviceAssignment, userId, userMainCallflowId)
], mainCallback);
},
usersUpdateCallflowNumbers: function(userId, callflowId, numbers, callback) {


Loading…
Cancel
Save