Merge pull request #666 from nasa/open656b

[Create] Utilize copyService during Save As
This commit is contained in:
Victor Woeltjen 2016-02-11 09:55:21 -08:00
commit 42ce2aa7cf
6 changed files with 130 additions and 74 deletions

View File

@ -156,14 +156,11 @@ define([
"name": "Save",
"description": "Save changes made to these objects.",
"depends": [
"$q",
"$location",
"$injector",
"urlService",
"navigationService",
"policyService",
"dialogService",
"creationService"
"creationService",
"copyService"
],
"priority": "mandatory"
},

View File

@ -36,18 +36,22 @@ define(
* @implements {Action}
* @memberof platform/commonUI/edit
*/
function SaveAction($q, $location, $injector, urlService, navigationService, policyService, dialogService, creationService, context) {
function SaveAction(
$injector,
policyService,
dialogService,
creationService,
copyService,
context
) {
this.domainObject = (context || {}).domainObject;
this.$location = $location;
this.injectObjectService = function(){
this.objectService = $injector.get("objectService");
};
this.urlService = urlService;
this.navigationService = navigationService;
this.policyService = policyService;
this.dialogService = dialogService;
this.creationService = creationService;
this.$q = $q;
this.copyService = copyService;
}
SaveAction.prototype.getObjectService = function(){
@ -67,35 +71,29 @@ define(
*/
SaveAction.prototype.perform = function () {
var domainObject = this.domainObject,
$location = this.$location,
urlService = this.urlService,
copyService = this.copyService,
self = this;
function resolveWith(object){
return function() {
return function () {
return object;
};
}
function doWizardSave(parent) {
var context = domainObject.getCapability("context"),
wizard = new CreateWizard(domainObject, parent, self.policyService);
wizard = new CreateWizard(
domainObject,
parent,
self.policyService
);
return self.dialogService
.getUserInput(wizard.getFormStructure(true), wizard.getInitialFormValue())
.then(function(formValue){
return wizard.populateObjectFromInput(formValue, domainObject);
});
}
function persistObject(object){
//Persist first to mark dirty
return object.getCapability('persistence').persist().then(function(){
//then save permanently
return object.getCapability('editor').save();
});
.getUserInput(
wizard.getFormStructure(true),
wizard.getInitialFormValue()
)
.then(wizard.populateObjectFromInput.bind(wizard));
}
function fetchObject(objectId){
@ -108,16 +106,18 @@ define(
return fetchObject(object.getModel().location);
}
function locateObjectInParent(parent){
parent.getCapability('composition').add(domainObject.getId());
return parent.getCapability('persistence').persist().then(function() {
return parent;
});
function allowClone(objectToClone) {
return (objectToClone.getId() === domainObject.getId()) ||
objectToClone.getCapability('location').isOriginal();
}
function doNothing() {
// Create cancelled, do nothing
return false;
function cloneIntoParent(parent) {
return copyService.perform(domainObject, parent, allowClone);
}
function cancelEditingAfterClone(clonedObject) {
return domainObject.getCapability("editor").cancel()
.then(resolveWith(clonedObject));
}
// Invoke any save behavior introduced by the editor capability;
@ -127,17 +127,13 @@ define(
function doSave() {
//This is a new 'virtual object' that has not been persisted
// yet.
if (!domainObject.getModel().persisted){
if (domainObject.getModel().persisted === undefined){
return getParent(domainObject)
.then(doWizardSave)
.then(persistObject)
.then(getParent)//Parent may have changed based
// on user selection
.then(locateObjectInParent)
.then(function(){
return fetchObject(domainObject.getId());
})
.catch(doNothing);
.then(doWizardSave)
.then(getParent)
.then(cloneIntoParent)
.then(cancelEditingAfterClone)
.catch(resolveWith(false));
} else {
return domainObject.getCapability("editor").save()
.then(resolveWith(domainObject.getOriginalObject()));
@ -148,7 +144,7 @@ define(
// UI, which will have been pushed atop the Browse UI.)
function returnToBrowse(object) {
if (object) {
self.navigationService.setNavigation(object);
object.getCapability("action").perform("navigate");
}
return object;
}

View File

@ -54,20 +54,52 @@ define(
);
};
/**
* A function used to check if a domain object should be cloned
* or not.
* @callback platform/entanglement.CopyService~filter
* @param {DomainObject} domainObject the object to be cloned
* @returns {boolean} true if the object should be cloned; false
* if it should be linked
*/
/**
* Creates a duplicate of the object tree starting at domainObject to
* the new parent specified.
* @param domainObject
* @param parent
* @param progress
*
* Any domain objects which cannot be created will not be cloned;
* instead, these will appear as links. If a filtering function
* is provided, any objects which fail that check will also be
* linked instead of cloned
*
* @param {DomainObject} domainObject the object to duplicate
* @param {DomainObject} parent the destination for the clone
* @param {platform/entanglement.CopyService~filter} [filter]
* an optional function used to filter out objects from
* the cloning process
* @returns a promise that will be completed with the clone of
* domainObject when the duplication is successful.
*/
CopyService.prototype.perform = function (domainObject, parent) {
var $q = this.$q,
copyTask = new CopyTask(domainObject, parent, this.policyService, this.$q);
CopyService.prototype.perform = function (domainObject, parent, filter) {
var policyService = this.policyService;
// Combines caller-provided filter (if any) with the
// baseline behavior of respecting creation policy.
function filterWithPolicy(domainObject) {
return (!filter || filter(domainObject)) &&
policyService.allow(
"creation",
domainObject.getCapability("type")
);
}
if (this.validate(domainObject, parent)) {
return copyTask.perform();
return new CopyTask(
domainObject,
parent,
filterWithPolicy,
this.$q
).perform();
} else {
throw new Error(
"Tried to copy objects without validating first."

View File

@ -31,18 +31,21 @@ define(
* This class encapsulates the process of copying a domain object
* and all of its children.
*
* @param domainObject The object to copy
* @param parent The new location of the cloned object tree
* @param $q
* @param {DomainObject} domainObject The object to copy
* @param {DomainObject} parent The new location of the cloned object tree
* @param {platform/entanglement.CopyService~filter} filter
* a function used to filter out objects from
* the cloning process
* @param $q Angular's $q, for promises
* @constructor
*/
function CopyTask (domainObject, parent, policyService, $q){
function CopyTask (domainObject, parent, filter, $q){
this.domainObject = domainObject;
this.parent = parent;
this.firstClone = undefined;
this.$q = $q;
this.deferred = undefined;
this.policyService = policyService;
this.filter = filter;
this.persisted = 0;
this.clones = [];
this.idMap = {};
@ -101,9 +104,14 @@ define(
* Will add a list of clones to the specified parent's composition
*/
function addClonesToParent(self) {
self.parent.getCapability("composition").add(self.firstClone.getId());
return self.parent.getCapability("persistence").persist()
.then(function(){return self.firstClone;});
return self.parent.getCapability("composition")
.add(self.firstClone)
.then(function (addedClone) {
return self.parent.getCapability("persistence").persist()
.then(function () {
return addedClone;
});
});
}
/**
@ -193,7 +201,7 @@ define(
//Check if the type of the object being copied allows for
// creation of new instances. If it does not, then a link to the
// original will be created instead.
if (this.policyService.allow("creation", originalObject.getCapability("type"))){
if (this.filter(originalObject)) {
//create a new clone of the original object. Use the
// creation capability of the targetParent to create the
// new clone. This will ensure that the correct persistence

View File

@ -162,6 +162,7 @@ define(
'compositionCapability',
['invoke', 'add']
);
compositionCapability.add.andCallFake(synchronousPromise);
locationCapability = jasmine.createSpyObj(
'locationCapability',
@ -387,6 +388,7 @@ define(
expect(childObjectClone.getModel().location).toEqual(objectClone.getId());
});
});
describe("when cloning non-creatable objects", function() {
beforeEach(function () {
policyService.allow.andCallFake(function(category){
@ -401,8 +403,33 @@ define(
it ("creates link instead of clone", function() {
var copiedObject = copyFinished.calls[0].args[0];
expect(copiedObject).toBe(object);
expect(compositionCapability.add).toHaveBeenCalledWith(copiedObject.getId());
//expect(newParent.getModel().composition).toContain(copiedObject.getId());
expect(compositionCapability.add)
.toHaveBeenCalledWith(copiedObject);
});
});
describe("when provided a filtering function", function () {
function accept() {
return true;
}
function reject() {
return false;
}
it("does not create new instances of objects " +
"rejected by the filter", function() {
copyService.perform(object, newParent, reject)
.then(copyFinished);
expect(copyFinished.mostRecentCall.args[0])
.toBe(object);
});
it("does create new instances of objects " +
"accepted by the filter", function() {
copyService.perform(object, newParent, accept)
.then(copyFinished);
expect(copyFinished.mostRecentCall.args[0])
.not.toBe(object);
});
});
});

View File

@ -44,11 +44,10 @@ define(
describe("CopyTask", function () {
var mockDomainObject,
mockParentObject,
mockPolicyService,
mockFilter,
mockQ,
mockDeferred,
testModel,
mockCallback,
counter,
cloneIds,
task;
@ -119,17 +118,14 @@ define(
mockParentObject = domainObjectFactory({
capabilities: makeMockCapabilities()
});
mockPolicyService = jasmine.createSpyObj(
'policyService',
[ 'allow' ]
);
mockFilter = jasmine.createSpy('filter');
mockQ = jasmine.createSpyObj('$q', ['when', 'defer', 'all']);
mockDeferred = jasmine.createSpyObj(
'deferred',
[ 'notify', 'resolve', 'reject' ]
);
mockPolicyService.allow.andReturn(true);
mockFilter.andReturn(true);
mockQ.when.andCallFake(synchronousPromise);
mockQ.defer.andReturn(mockDeferred);
@ -156,7 +152,7 @@ define(
task = new CopyTask(
mockDomainObject,
mockParentObject,
mockPolicyService,
mockFilter,
mockQ
);
@ -218,7 +214,7 @@ define(
task = new CopyTask(
mockComposingObject,
mockParentObject,
mockPolicyService,
mockFilter,
mockQ
);