Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/api/2.0/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ var nodesMasterDelTagById = controller({success: 204}, function(req) {
var nodesDelRelations = controller(function(req) {
return nodes.editNodeRelations(req.swagger.params.identifier.value,
req.body,
nodes._removeRelation
nodes.removeRelation
);
});

var nodesAddRelations = controller(function(req) {
return nodes.editNodeRelations(req.swagger.params.identifier.value,
req.body,
nodes._addRelation
nodes.addRelation
);
});

Expand Down
214 changes: 84 additions & 130 deletions lib/services/nodes-api-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,19 @@ function nodeApiServiceFactory(
};

/**
* Remove the relations to original node in target node. If the node is invalid
* Remove targets from node relations. If the node is invalid
* or doesn't have required relation, this function doesn't need to update the
* node info and ignore silently with Promise.resolve(). If the relation field
* is empty after removing, delete this field. If the node to be updated is a component
* of a node delete it
* node info and ignore silently with Promise.resolve().
* If the targets list of one relation item is empty after removing, delete this relations.
* @param {Object} node node whose relation needs to be updated
* @param {String} type relation type that needs to be updated
* @param {String[] | Object[]} targets - nodes or ids in the relation
* that needs to be deleted
* @return {Object} node after removing relation
*/
NodeApiService.prototype._removeRelation = function removeRelation(node, type, targets) {
NodeApiService.prototype.removeRelation = function removeRelation(node, type, targets) {
var self = this;

if (!node || !type || !_.has(node, 'relations')) {
return;
}
Expand All @@ -93,15 +94,25 @@ function nodeApiServiceFactory(
return;
}

// Remove target node id in relation field
targets = [].concat(targets).map(function(node) {return node.id || node;});
node.relations[index].targets = _.difference(node.relations[index].targets, targets);
// Remove the type of relation if no targets in it
// Remove relations with blank targets
if (node.relations[index].targets.length === 0) {
_.pull(node.relations, node.relations[index]);
var relationsToBeRemoved = {"relations": [node.relations[index]]};
return waterline.nodes.removeListItemsByIdentifier(node.id, relationsToBeRemoved);
}

if (!targets){
return Promise.resolve(node);
}

return node;
// Remove target node id in relation field
targets = [].concat(targets).map(function(node) {return node.id || node;});
var field = ["relations.", String(index),".targets"].join("");
var targetsToBeRemoved = {};
targetsToBeRemoved[field] = targets;
return waterline.nodes.removeListItemsByIdentifier(node.id, targetsToBeRemoved)
.then(function(modifiedNode){
return self.removeRelation(modifiedNode, type);
});
};

/**
Expand All @@ -113,89 +124,67 @@ function nodeApiServiceFactory(
* @param {String[] | Object[]} targets - nodes or ids in relation type that needs to be added
* @return {Object} the updated node
*/
NodeApiService.prototype._addRelation = function addRelation(node, type, targets) {
NodeApiService.prototype.addRelation = function addRelation(node, type, targets) {
if (!(node && type && targets)) {
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, you'd better return a Promise as below code return a Promise.

}
return waterline.nodes.addFieldIfNotExistByIdentifier(node.id, "relations", [])
.then(function(){
var relationsItemToBeAdded = {
relations: [{relationType: type, targets: []}]
};
var existSign = [{relationType: type}];
return waterline.nodes.addListItemsIfNotExistByIdentifier(
node.id,
relationsItemToBeAdded,
existSign
);
})
.then(function(modifiedNode){
if (!modifiedNode){
return node;
}
return modifiedNode;
})
.then(function(modifiedNode){
var targetsItems = _.map([].concat(targets), function(targetNode) {
targetNode = targetNode.id || targetNode;
if(targetNode === node.id ) {
return Promise.reject(
new Error('Node cannot have relationship '+type+' with itself'));
}
return targetNode;
});
var index = _.findIndex(modifiedNode.relations, { relationType: type });
var field = ["relations.", String(index),".targets"].join("");
var targetsToBeAdded = {};
targetsToBeAdded[field] = _.uniq(targetsItems);

// Can not make sure prevent every exception in high concurrency.
if (type === 'containedBy' &&
modifiedNode.relations[index].targets.length + targets.length > 1) {
return Promise.reject(
new Error("Node "+node.id+" can only be contained by one node"));
}

node.relations = (node.relations || []);

targets = _.map([].concat(targets), function(targetNode) {
targetNode = targetNode.id || targetNode;
if(targetNode === node.id ) {
throw new Error('Node cannot have relationship '+type+' with itself');
// Compute node can only have one enclosure target.
if (type === "enclosedBy") {
var targetsToBeRemoveded = {};
targetsToBeRemoveded[field] = [modifiedNode.relations[index].targets[0]];
return waterline.nodes.removeListItemsByIdentifier(
node.id, targetsToBeRemoveded
)
.then(function(){
return targetsToBeAdded;
});
}
return targetNode;
return targetsToBeAdded;
})
.then(function(targetsToBeAdded){
return waterline.nodes.addListItemsIfNotExistByIdentifier(
node.id, targetsToBeAdded
);
});
var index = _.findIndex(node.relations, { relationType: type });
if (index === -1) {
node.relations.push({relationType: type, targets: _.uniq(targets)});
} else {
node.relations[index].targets = _.uniq(node.relations[index].targets.concat(targets));
}
index = index === -1 ? node.relations.length - 1: index;
if (type === 'containedBy' && node.relations[index].targets.length > 1) {
throw new Error("Node "+node.id+" can only be contained by one node");
}

return node;
};

/**
* Get the targets to be removed in node. If the node is invalid
* or doesn't have required targets, this function doesn't need to return the
* node relation info and ignore silently with Promise.resolve().
* @param {Object} node node whose relation needs to be updated
* @param {String} type relation type that needs to be updated
* @param {String[] | Object[]} targets - nodes or ids in the relation
* that needs to be deleted
* @return {Object} {indexPath: [tagsToBeRemoved]}
*/
NodeApiService.prototype._getTargetsToBeRemoved =
function getTargetsToBeRemoved(node, type, targets){
if (!node || !type || !_.has(node, 'relations')) {
return;
}

var index = _.findIndex(node.relations, { relationType: type });
if (index === -1 || !_.has(node.relations[index], 'targets')) {
return;
}

targets = [].concat(targets).map(function(node) {return node.id || node;});
var indexPath = ["relations.", String(index),".targets"].join("");
var values = {};
values[indexPath] = [].concat(targets);
return values;
};

/**
* Get the relations to be removed in node. Relations with [targets] shoud be removed.
* If the node is invalid or doesn't have required relation, this function doesn't need to
* return the node relation info and ignore silently with Promise.resolve().
* @param {Object} node node whose relation needs to be updated
* @param {String} type relation type that needs to be updated
* @param {String[] | Object[]} targets - nodes or ids in the relation
* that needs to be deleted
* @return {Object} {indexPath: [tagsToBeRemoved]}
*/
NodeApiService.prototype._getRelationsToBeRemoved =
function getRelationsToBeRemoved(node, type){
if (!node || !type || !_.has(node, 'relations')) {
return;
}

var index = _.findIndex(node.relations, { relationType: type });
if (index === -1 || !_.has(node.relations[index], 'targets')) {
return;
}

if (node.relations[index].targets.length !== 0) {
return;
}

var values = {"relations": [node.relations[index]]};
return values;
};

/**
Expand Down Expand Up @@ -258,28 +247,11 @@ function nodeApiServiceFactory(
});
} else {
return Promise.map(targetNodes, function(targetNode) {
var targets = self._getTargetsToBeRemoved(
return self.removeRelation(
targetNode,
Constants.NodeRelations[type].mapping,
node.id
);
if (!targets) {
return;
}
return waterline.nodes
.removeListItemsByIdentifier(targetNode.id, targets)
.then(function(modifiedTargetNode){
var relations = self._getRelationsToBeRemoved(
modifiedTargetNode,
Constants.NodeRelations[type].mapping,
node.id
);
if (!relations) {
return;
}
return waterline.nodes
.removeListItemsByIdentifier(targetNode.id, relations);
});
});
}
});
Expand Down Expand Up @@ -437,44 +409,26 @@ function nodeApiServiceFactory(
)
);
result.push(relationType);
}, [])
);
}, []));
})
.then(function(targetRelations) {
var parentNode = this.parentNode;
targetRelations = _.chunk(targetRelations, 2); //divide the array into subArray chunks
//of [[targetNodes], relationType]

var updatedNodes = _.transform(targetRelations, function(result, relationSet) {
return Promise.all(_.compact(_.map(targetRelations, function(relationSet){
var targetNodes = relationSet[0];
var relationType = relationSet[1];

if (!Constants.NodeRelations[relationType]) {
return;
}
result[parentNode.id] = handler.call(

return handler.call(
self,
result[parentNode.id] || parentNode,
parentNode,
relationType,
targetNodes
);

_.forEach(targetNodes, function(node) {
result[node.id] = handler.call(
self,
result[node.id] || node,
Constants.NodeRelations[relationType].mapping,
parentNode
);
});
}, {});

return Promise.all(_.compact(_.map(updatedNodes, function(updatedNode) {
if (!updatedNode) {
return;
}
return waterline.nodes.updateByIdentifier(
updatedNode.id, {relations: updatedNode.relations}
);
})));
});
};
Expand Down
4 changes: 2 additions & 2 deletions spec/lib/api/2.0/nodes-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ describe('2.0 Http.Api.Nodes', function () {
expect(data.body[0].relations).to.deep.equal(updatedComputeNode.relations);
expect(data.body[1].relations).to.deep.equal(updatedRackNode.relations);
expect(nodeApiService.editNodeRelations).to.be.calledWithExactly(
'1234',relationUpdate, nodeApiService._addRelation
'1234',relationUpdate, nodeApiService.addRelation
);
});

Expand Down Expand Up @@ -356,7 +356,7 @@ describe('2.0 Http.Api.Nodes', function () {
expect(data.body[0].relations).to.deep.equal(updatedComputeNode.relations);
expect(data.body[1].relations).to.deep.equal(updatedRackNode.relations);
expect(nodeApiService.editNodeRelations).to.be.calledWithExactly(
'1234', relationDeletion, nodeApiService._removeRelation
'1234', relationDeletion, nodeApiService.removeRelation
);
});
});
Expand Down
Loading