diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 1094abb864..d2e9a684ae 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -200,7 +200,6 @@ define([ "name": "Remove", "description": "Remove this object from its containing object.", "depends": [ - "$q", "navigationService" ] }, diff --git a/platform/commonUI/edit/src/actions/EditAndComposeAction.js b/platform/commonUI/edit/src/actions/EditAndComposeAction.js index a6dc32b0d9..f1dc5e47d3 100644 --- a/platform/commonUI/edit/src/actions/EditAndComposeAction.js +++ b/platform/commonUI/edit/src/actions/EditAndComposeAction.js @@ -40,19 +40,11 @@ define( var self = this, editAction = this.domainObject.getCapability('action').getActions("edit")[0]; - // Persist changes to the domain object - function doPersist() { - var persistence = - self.domainObject.getCapability('persistence'); - return persistence.persist(); - } - // Link these objects function doLink() { var composition = self.domainObject && self.domainObject.getCapability('composition'); - return composition && composition.add(self.selectedObject) - .then(doPersist); + return composition && composition.add(self.selectedObject); } if (editAction) { diff --git a/platform/commonUI/edit/src/actions/PropertiesAction.js b/platform/commonUI/edit/src/actions/PropertiesAction.js index 43ebe215ad..e17e0053f1 100644 --- a/platform/commonUI/edit/src/actions/PropertiesAction.js +++ b/platform/commonUI/edit/src/actions/PropertiesAction.js @@ -50,12 +50,6 @@ define( domainObject = this.domainObject, dialogService = this.dialogService; - // Persist modifications to this domain object - function doPersist() { - var persistence = domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - // Update the domain object model based on user input function updateModel(userInput, dialog) { return domainObject.useCapability('mutation', function (model) { @@ -73,11 +67,9 @@ define( dialog.getFormStructure(), dialog.getInitialFormValue() ).then(function (userInput) { - // Update the model, if user input was provided - return userInput && updateModel(userInput, dialog); - }).then(function (result) { - return result && doPersist(); - }); + // Update the model, if user input was provided + return userInput && updateModel(userInput, dialog); + }); } return type && showDialog(type); @@ -94,9 +86,7 @@ define( creatable = type && type.hasFeature('creation'); // Only allow creatable types to be edited - return domainObject && - domainObject.hasCapability("persistence") && - creatable; + return domainObject && creatable; }; return PropertiesAction; diff --git a/platform/commonUI/edit/src/actions/RemoveAction.js b/platform/commonUI/edit/src/actions/RemoveAction.js index ab460f522d..604d4f0f0b 100644 --- a/platform/commonUI/edit/src/actions/RemoveAction.js +++ b/platform/commonUI/edit/src/actions/RemoveAction.js @@ -39,9 +39,8 @@ define( * @constructor * @implements {Action} */ - function RemoveAction($q, navigationService, context) { + function RemoveAction(navigationService, context) { this.domainObject = (context || {}).domainObject; - this.$q = $q; this.navigationService = navigationService; } @@ -51,8 +50,7 @@ define( * fulfilled when the action has completed. */ RemoveAction.prototype.perform = function () { - var $q = this.$q, - navigationService = this.navigationService, + var navigationService = this.navigationService, domainObject = this.domainObject; /* * Check whether an object ID matches the ID of the object being @@ -71,15 +69,6 @@ define( model.composition = model.composition.filter(isNotObject); } - /* - * Invoke persistence on a domain object. This will be called upon - * the removed object's parent (as its composition will have changed.) - */ - function doPersist(domainObj) { - var persistence = domainObj.getCapability('persistence'); - return persistence && persistence.persist(); - } - /* * Checks current object and ascendants of current * object with object being removed, if the current @@ -119,15 +108,10 @@ define( // navigates to existing object up tree checkObjectNavigation(object, parent); - return $q.when( - parent.useCapability('mutation', doMutate) - ).then(function () { - return doPersist(parent); - }); + return parent.useCapability('mutation', doMutate); } - return $q.when(domainObject) - .then(removeFromContext); + return removeFromContext(domainObject); }; // Object needs to have a parent for Remove to be applicable diff --git a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js index bcda009c56..f9467326c0 100644 --- a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js +++ b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js @@ -81,6 +81,10 @@ define( return this.persistenceCapability.getSpace(); }; + TransactionalPersistenceCapability.prototype.persisted = function () { + return this.persistenceCapability.persisted(); + }; + return TransactionalPersistenceCapability; } ); diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 33613f8a17..81252977b1 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -50,17 +50,13 @@ define( this.listenHandle = undefined; // Mutate and persist a new version of a domain object's model. - function doPersist(model) { + function doMutate(model) { var domainObject = self.domainObject; // First, mutate; then, persist. return $q.when(domainObject.useCapability("mutation", function () { return model; - })).then(function (result) { - // Only persist when mutation was successful - return result && - domainObject.getCapability("persistence").persist(); - }); + })); } // Handle changes to model and/or view configuration @@ -80,14 +76,14 @@ define( ].join(" ")); // Update the configuration stored in the model, and persist. - if (domainObject && domainObject.hasCapability("persistence")) { + if (domainObject) { // Configurations for specific views are stored by // key in the "configuration" field of the model. if (self.key && configuration) { model.configuration = model.configuration || {}; model.configuration[self.key] = configuration; } - doPersist(model); + doMutate(model); } } diff --git a/platform/commonUI/edit/src/services/NestedTransaction.js b/platform/commonUI/edit/src/services/NestedTransaction.js new file mode 100644 index 0000000000..c7fcee4d5e --- /dev/null +++ b/platform/commonUI/edit/src/services/NestedTransaction.js @@ -0,0 +1,48 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +define(['./Transaction'], function (Transaction) { + /** + * A nested transaction is a transaction which takes place in the context + * of a larger parent transaction. It becomes part of the parent + * transaction when (and only when) committed. + * @param parent + * @constructor + * @extends {platform/commonUI/edit/services.Transaction} + * @memberof platform/commonUI/edit/services + */ + function NestedTransaction(parent) { + this.parent = parent; + Transaction.call(this, parent.$log); + } + + NestedTransaction.prototype = Object.create(Transaction.prototype); + + NestedTransaction.prototype.commit = function () { + this.parent.add( + Transaction.prototype.commit.bind(this), + Transaction.prototype.cancel.bind(this) + ); + return Promise.resolve(true); + }; + + return NestedTransaction; +}); diff --git a/platform/commonUI/edit/src/services/Transaction.js b/platform/commonUI/edit/src/services/Transaction.js new file mode 100644 index 0000000000..803536be4d --- /dev/null +++ b/platform/commonUI/edit/src/services/Transaction.js @@ -0,0 +1,96 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +define([], function () { + /** + * A Transaction represents a set of changes that are intended to + * be kept or discarded as a unit. + * @param $log Angular's `$log` service, for logging messages + * @constructor + * @memberof platform/commonUI/edit/services + */ + function Transaction($log) { + this.$log = $log; + this.callbacks = []; + } + + /** + * Add a change to the current transaction, as expressed by functions + * to either keep or discard the change. + * @param {Function} commit called when the transaction is committed + * @param {Function} cancel called when the transaction is cancelled + * @returns {Function) a function which may be called to remove this + * pair of callbacks from the transaction + */ + Transaction.prototype.add = function (commit, cancel) { + var callback = { commit: commit, cancel: cancel }; + this.callbacks.push(callback); + return function () { + this.callbacks = this.callbacks.filter(function (c) { + return c !== callback; + }); + }.bind(this); + }; + + /** + * Get the number of changes in the current transaction. + * @returns {number} the size of the current transaction + */ + Transaction.prototype.size = function () { + return this.callbacks.length; + }; + + /** + * Keep all changes associated with this transaction. + * @method {platform/commonUI/edit/services.Transaction#commit} + * @returns {Promise} a promise which will resolve when all callbacks + * have been handled. + */ + + /** + * Discard all changes associated with this transaction. + * @method {platform/commonUI/edit/services.Transaction#cancel} + * @returns {Promise} a promise which will resolve when all callbacks + * have been handled. + */ + + ['commit', 'cancel'].forEach(function (method) { + Transaction.prototype[method] = function () { + var promises = []; + var callback; + + while (this.callbacks.length > 0) { + callback = this.callbacks.shift(); + try { + promises.push(callback[method]()); + } catch (e) { + this.$log + .error("Error trying to " + method + " transaction."); + } + } + + return Promise.all(promises); + }; + }); + + + return Transaction; +}); diff --git a/platform/commonUI/edit/src/services/TransactionService.js b/platform/commonUI/edit/src/services/TransactionService.js index df0a051f0d..3c234ca882 100644 --- a/platform/commonUI/edit/src/services/TransactionService.js +++ b/platform/commonUI/edit/src/services/TransactionService.js @@ -21,8 +21,8 @@ *****************************************************************************/ /*global define*/ define( - [], - function () { + ['./Transaction', './NestedTransaction'], + function (Transaction, NestedTransaction) { /** * Implements an application-wide transaction state. Once a * transaction is started, calls to @@ -37,10 +37,7 @@ define( function TransactionService($q, $log) { this.$q = $q; this.$log = $log; - this.transaction = false; - - this.onCommits = []; - this.onCancels = []; + this.transactions = []; } /** @@ -50,18 +47,18 @@ define( * #cancel} are called */ TransactionService.prototype.startTransaction = function () { - if (this.transaction) { - //Log error because this is a programming error if it occurs. - this.$log.error("Transaction already in progress"); - } - this.transaction = true; + var transaction = this.isActive() ? + new NestedTransaction(this.transactions[0]) : + new Transaction(this.$log); + + this.transactions.push(transaction); }; /** * @returns {boolean} If true, indicates that a transaction is in progress */ TransactionService.prototype.isActive = function () { - return this.transaction; + return this.transactions.length > 0; }; /** @@ -72,24 +69,20 @@ define( * @param onCancel A function to call on cancel */ TransactionService.prototype.addToTransaction = function (onCommit, onCancel) { - if (this.transaction) { - this.onCommits.push(onCommit); - if (onCancel) { - this.onCancels.push(onCancel); - } + if (this.isActive()) { + return this.activeTransaction().add(onCommit, onCancel); } else { //Log error because this is a programming error if it occurs. this.$log.error("No transaction in progress"); } + }; - return function () { - this.onCommits = this.onCommits.filter(function (callback) { - return callback !== onCommit; - }); - this.onCancels = this.onCancels.filter(function (callback) { - return callback !== onCancel; - }); - }.bind(this); + /** + * Get the transaction at the top of the stack. + * @private + */ + TransactionService.prototype.activeTransaction = function () { + return this.transactions[this.transactions.length - 1]; }; /** @@ -100,24 +93,8 @@ define( * completed. Will reject if any commit operations fail */ TransactionService.prototype.commit = function () { - var self = this, - promises = [], - onCommit; - - while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction - onCommit = this.onCommits.pop(); - try { // ...also don't want to fail mid-loop... - promises.push(onCommit()); - } catch (e) { - this.$log.error("Error committing transaction."); - } - } - return this.$q.all(promises).then(function () { - self.transaction = false; - - self.onCommits = []; - self.onCancels = []; - }); + var transaction = this.transactions.pop(); + return transaction ? transaction.commit() : Promise.reject(); }; /** @@ -129,28 +106,17 @@ define( * @returns {*} */ TransactionService.prototype.cancel = function () { - var self = this, - results = [], - onCancel; - - while (this.onCancels.length > 0) { - onCancel = this.onCancels.pop(); - try { - results.push(onCancel()); - } catch (error) { - this.$log.error("Error cancelling transaction."); - } - } - return this.$q.all(results).then(function () { - self.transaction = false; - - self.onCommits = []; - self.onCancels = []; - }); + var transaction = this.transactions.pop(); + return transaction ? transaction.cancel() : Promise.reject(); }; + /** + * Get the size (the number of commit/cancel callbacks) of + * the active transaction. + * @returns {number} size of the active transaction + */ TransactionService.prototype.size = function () { - return this.onCommits.length; + return this.isActive() ? this.activeTransaction().size() : 0; }; return TransactionService; diff --git a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js b/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js index 25ec4567a6..0010a92735 100644 --- a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js +++ b/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js @@ -30,7 +30,6 @@ define( mockParent, mockContext, mockComposition, - mockPersistence, mockActionCapability, mockEditAction, mockType, @@ -68,7 +67,6 @@ define( }; mockContext = jasmine.createSpyObj("context", ["getParent"]); mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockType = jasmine.createSpyObj("type", ["hasFeature", "getKey"]); mockActionCapability = jasmine.createSpyObj("actionCapability", ["getActions"]); mockEditAction = jasmine.createSpyObj("editAction", ["perform"]); @@ -84,7 +82,6 @@ define( capabilities = { composition: mockComposition, - persistence: mockPersistence, action: mockActionCapability, type: mockType }; @@ -107,11 +104,6 @@ define( .toHaveBeenCalledWith(mockDomainObject); }); - it("persists changes afterward", function () { - action.perform(); - expect(mockPersistence.persist).toHaveBeenCalled(); - }); - it("enables edit mode for objects that have an edit action", function () { mockActionCapability.getActions.andReturn([mockEditAction]); action.perform(); diff --git a/platform/commonUI/edit/test/actions/PropertiesActionSpec.js b/platform/commonUI/edit/test/actions/PropertiesActionSpec.js index 8123a506f4..20e32ab015 100644 --- a/platform/commonUI/edit/test/actions/PropertiesActionSpec.js +++ b/platform/commonUI/edit/test/actions/PropertiesActionSpec.js @@ -43,7 +43,6 @@ define( }, hasFeature: jasmine.createSpy('hasFeature') }, - persistence: jasmine.createSpyObj("persistence", ["persist"]), mutation: jasmine.createSpy("mutation") }; model = {}; @@ -78,25 +77,18 @@ define( action = new PropertiesAction(dialogService, context); }); - it("persists when an action is performed", function () { - action.perform(); - expect(capabilities.persistence.persist) - .toHaveBeenCalled(); - }); - - it("does not persist any changes upon cancel", function () { - input = undefined; - action.perform(); - expect(capabilities.persistence.persist) - .not.toHaveBeenCalled(); - }); - it("mutates an object when performed", function () { action.perform(); expect(capabilities.mutation).toHaveBeenCalled(); capabilities.mutation.mostRecentCall.args[0]({}); }); + it("does not muate object upon cancel", function () { + input = undefined; + action.perform(); + expect(capabilities.mutation).not.toHaveBeenCalled(); + }); + it("is only applicable when a domain object is in context", function () { expect(PropertiesAction.appliesTo(context)).toBeTruthy(); expect(PropertiesAction.appliesTo({})).toBeFalsy(); diff --git a/platform/commonUI/edit/test/actions/RemoveActionSpec.js b/platform/commonUI/edit/test/actions/RemoveActionSpec.js index 7830ca0293..c5755b3636 100644 --- a/platform/commonUI/edit/test/actions/RemoveActionSpec.js +++ b/platform/commonUI/edit/test/actions/RemoveActionSpec.js @@ -37,7 +37,6 @@ define( mockGrandchildContext, mockRootContext, mockMutation, - mockPersistence, mockType, actionContext, model, @@ -53,8 +52,6 @@ define( } beforeEach(function () { - - mockDomainObject = jasmine.createSpyObj( "domainObject", ["getId", "getCapability"] @@ -88,7 +85,6 @@ define( mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]); mockRootContext = jasmine.createSpyObj("context", ["getParent"]); mockMutation = jasmine.createSpyObj("mutation", ["invoke"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockType = jasmine.createSpyObj("type", ["hasFeature"]); mockNavigationService = jasmine.createSpyObj( "navigationService", @@ -109,7 +105,6 @@ define( capabilities = { mutation: mockMutation, - persistence: mockPersistence, type: mockType }; model = { @@ -118,7 +113,7 @@ define( actionContext = { domainObject: mockDomainObject }; - action = new RemoveAction(mockQ, mockNavigationService, actionContext); + action = new RemoveAction(mockNavigationService, actionContext); }); it("only applies to objects with parents", function () { @@ -154,9 +149,6 @@ define( // Should have removed "test" - that was our // mock domain object's id. expect(result.composition).toEqual(["a", "b"]); - - // Finally, should have persisted - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("removes parent of object currently navigated to", function () { diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js index 7af3740fd6..cb766f95a7 100644 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js @@ -30,7 +30,6 @@ define( mockScope, testRepresentation, mockDomainObject, - mockPersistence, mockStatusCapability, mockEditorCapability, mockCapabilities, @@ -56,15 +55,12 @@ define( "useCapability", "hasCapability" ]); - mockPersistence = - jasmine.createSpyObj("persistence", ["persist"]); mockStatusCapability = jasmine.createSpyObj("statusCapability", ["listen"]); mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]); mockCapabilities = { - 'persistence': mockPersistence, 'status': mockStatusCapability, 'editor': mockEditorCapability }; @@ -96,7 +92,7 @@ define( expect(representer.listenHandle).toHaveBeenCalled(); }); - it("mutates and persists upon observed changes", function () { + it("mutates upon observed changes", function () { mockScope.model = { someKey: "some value" }; mockScope.configuration = { someConfiguration: "something" }; @@ -108,9 +104,6 @@ define( jasmine.any(Function) ); - // ... and should have persisted the mutation - expect(mockPersistence.persist).toHaveBeenCalled(); - // Finally, check that the provided mutation function // includes both model and configuration expect( diff --git a/platform/commonUI/edit/test/services/NestedTransactionSpec.js b/platform/commonUI/edit/test/services/NestedTransactionSpec.js new file mode 100644 index 0000000000..df82c12a78 --- /dev/null +++ b/platform/commonUI/edit/test/services/NestedTransactionSpec.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define(["../../src/services/NestedTransaction"], function (NestedTransaction) { + var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size']; + + describe("A NestedTransaction", function () { + var mockTransaction, + nestedTransaction; + + beforeEach(function () { + mockTransaction = + jasmine.createSpyObj('transaction', TRANSACTION_METHODS); + nestedTransaction = new NestedTransaction(mockTransaction); + }); + + it("exposes a Transaction's interface", function () { + TRANSACTION_METHODS.forEach(function (method) { + expect(nestedTransaction[method]) + .toEqual(jasmine.any(Function)); + }); + }); + + describe("when callbacks are added", function () { + var mockCommit, + mockCancel, + remove; + + beforeEach(function () { + mockCommit = jasmine.createSpy('commit'); + mockCancel = jasmine.createSpy('cancel'); + remove = nestedTransaction.add(mockCommit, mockCancel); + }); + + it("does not interact with its parent transaction", function () { + TRANSACTION_METHODS.forEach(function (method) { + expect(mockTransaction[method]) + .not.toHaveBeenCalled(); + }); + }); + + describe("and the transaction is committed", function () { + beforeEach(function () { + nestedTransaction.commit(); + }); + + it("adds to its parent transaction", function () { + expect(mockTransaction.add).toHaveBeenCalledWith( + jasmine.any(Function), + jasmine.any(Function) + ); + }); + }); + }); + }); +}); + + diff --git a/platform/commonUI/edit/test/services/TransactionServiceSpec.js b/platform/commonUI/edit/test/services/TransactionServiceSpec.js index 8c4d635a6f..f05fb9df3d 100644 --- a/platform/commonUI/edit/test/services/TransactionServiceSpec.js +++ b/platform/commonUI/edit/test/services/TransactionServiceSpec.js @@ -57,8 +57,7 @@ define( transactionService.startTransaction(); transactionService.addToTransaction(onCommit, onCancel); - expect(transactionService.onCommits.length).toBe(1); - expect(transactionService.onCancels.length).toBe(1); + expect(transactionService.size()).toBe(1); }); it("size function returns size of commit and cancel queues", function () { @@ -85,7 +84,7 @@ define( }); it("commit calls all queued commit functions", function () { - expect(transactionService.onCommits.length).toBe(3); + expect(transactionService.size()).toBe(3); transactionService.commit(); onCommits.forEach(function (spy) { expect(spy).toHaveBeenCalled(); @@ -95,8 +94,8 @@ define( it("commit resets active state and clears queues", function () { transactionService.commit(); expect(transactionService.isActive()).toBe(false); - expect(transactionService.onCommits.length).toBe(0); - expect(transactionService.onCancels.length).toBe(0); + expect(transactionService.size()).toBe(0); + expect(transactionService.size()).toBe(0); }); }); @@ -116,7 +115,7 @@ define( }); it("cancel calls all queued cancel functions", function () { - expect(transactionService.onCancels.length).toBe(3); + expect(transactionService.size()).toBe(3); transactionService.cancel(); onCancels.forEach(function (spy) { expect(spy).toHaveBeenCalled(); @@ -126,8 +125,7 @@ define( it("cancel resets active state and clears queues", function () { transactionService.cancel(); expect(transactionService.isActive()).toBe(false); - expect(transactionService.onCommits.length).toBe(0); - expect(transactionService.onCancels.length).toBe(0); + expect(transactionService.size()).toBe(0); }); }); diff --git a/platform/commonUI/edit/test/services/TransactionSpec.js b/platform/commonUI/edit/test/services/TransactionSpec.js new file mode 100644 index 0000000000..3b555f3d05 --- /dev/null +++ b/platform/commonUI/edit/test/services/TransactionSpec.js @@ -0,0 +1,110 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define( + ["../../src/services/Transaction"], + function (Transaction) { + + describe("A Transaction", function () { + var mockLog, + transaction; + + beforeEach(function () { + mockLog = jasmine.createSpyObj( + '$log', + ['warn', 'info', 'error', 'debug'] + ); + transaction = new Transaction(mockLog); + }); + + it("initially has a size of zero", function () { + expect(transaction.size()).toEqual(0); + }); + + describe("when callbacks are added", function () { + var mockCommit, + mockCancel, + remove; + + beforeEach(function () { + mockCommit = jasmine.createSpy('commit'); + mockCancel = jasmine.createSpy('cancel'); + remove = transaction.add(mockCommit, mockCancel); + }); + + it("reports a new size", function () { + expect(transaction.size()).toEqual(1); + }); + + it("returns a function to remove those callbacks", function () { + expect(remove).toEqual(jasmine.any(Function)); + remove(); + expect(transaction.size()).toEqual(0); + }); + + describe("and the transaction is committed", function () { + beforeEach(function () { + transaction.commit(); + }); + + it("triggers the commit callback", function () { + expect(mockCommit).toHaveBeenCalled(); + }); + + it("does not trigger the cancel callback", function () { + expect(mockCancel).not.toHaveBeenCalled(); + }); + }); + + describe("and the transaction is cancelled", function () { + beforeEach(function () { + transaction.cancel(); + }); + + it("triggers the cancel callback", function () { + expect(mockCancel).toHaveBeenCalled(); + }); + + it("does not trigger the commit callback", function () { + expect(mockCommit).not.toHaveBeenCalled(); + }); + }); + + describe("and an exception is encountered during commit", function () { + beforeEach(function () { + mockCommit.andCallFake(function () { + throw new Error("test error"); + }); + transaction.commit(); + }); + + it("logs an error", function () { + expect(mockLog.error).toHaveBeenCalled(); + }); + }); + }); + + }); + } +); + diff --git a/platform/core/bundle.js b/platform/core/bundle.js index 4b9e2b9adb..26a49e16d9 100644 --- a/platform/core/bundle.js +++ b/platform/core/bundle.js @@ -46,6 +46,7 @@ define([ "./src/capabilities/MutationCapability", "./src/capabilities/DelegationCapability", "./src/capabilities/InstantiationCapability", + "./src/runs/TransactingMutationListener", "./src/services/Now", "./src/services/Throttle", "./src/services/Topic", @@ -78,6 +79,7 @@ define([ MutationCapability, DelegationCapability, InstantiationCapability, + TransactingMutationListener, Now, Throttle, Topic, @@ -417,6 +419,12 @@ define([ } } ], + "runs": [ + { + "implementation": TransactingMutationListener, + "depends": ["topic", "transactionService"] + } + ], "constants": [ { "key": "PERSISTENCE_SPACE", diff --git a/platform/core/src/capabilities/CompositionCapability.js b/platform/core/src/capabilities/CompositionCapability.js index f7c2fb1a3c..3f5769005f 100644 --- a/platform/core/src/capabilities/CompositionCapability.js +++ b/platform/core/src/capabilities/CompositionCapability.js @@ -49,8 +49,7 @@ define( } /** - * Add a domain object to the composition of the field. - * This mutates but does not persist the modified object. + * Add a domain object to the composition of this domain object. * * If no index is given, this is added to the end of the composition. * diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js index 275080368b..54fa537c64 100644 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ b/platform/core/src/capabilities/PersistenceCapability.js @@ -113,11 +113,16 @@ define( domainObject = this.domainObject, model = domainObject.getModel(), modified = model.modified, + persisted = model.persisted, persistenceService = this.persistenceService, - persistenceFn = model.persisted !== undefined ? + persistenceFn = persisted !== undefined ? this.persistenceService.updateObject : this.persistenceService.createObject; + if (persisted !== undefined && persisted === modified) { + return this.$q.when(true); + } + // Update persistence timestamp... domainObject.useCapability("mutation", function (m) { m.persisted = modified; @@ -178,6 +183,15 @@ define( }; + /** + * Check if this domain object has been persisted at some + * point. + * @returns {boolean} true if the object has been persisted + */ + PersistenceCapability.prototype.persisted = function () { + return this.domainObject.getModel().persisted !== undefined; + }; + /** * Get the key for this domain object in the given space. * diff --git a/platform/core/src/runs/TransactingMutationListener.js b/platform/core/src/runs/TransactingMutationListener.js new file mode 100644 index 0000000000..c534c6fae2 --- /dev/null +++ b/platform/core/src/runs/TransactingMutationListener.js @@ -0,0 +1,54 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define([], function () { + /** + * Listens for mutation on domain objects and triggers persistence when + * it occurs. + * @param {Topic} topic the `topic` service; used to listen for mutation + * @memberof platform/core + */ + function TransactingMutationListener(topic, transactionService) { + var mutationTopic = topic('mutation'); + mutationTopic.listen(function (domainObject) { + var persistence = domainObject.getCapability('persistence'); + var wasActive = transactionService.isActive(); + if (persistence.persisted()) { + if (!wasActive) { + transactionService.startTransaction(); + } + + transactionService.addToTransaction( + persistence.persist.bind(persistence), + persistence.refresh.bind(persistence) + ); + + if (!wasActive) { + transactionService.commit(); + } + } + }); + } + + return TransactingMutationListener; +}); diff --git a/platform/core/test/runs/TransactingMutationListenerSpec.js b/platform/core/test/runs/TransactingMutationListenerSpec.js new file mode 100644 index 0000000000..c371d25db9 --- /dev/null +++ b/platform/core/test/runs/TransactingMutationListenerSpec.js @@ -0,0 +1,120 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define( + ["../../src/runs/TransactingMutationListener"], + function (TransactingMutationListener) { + + describe("TransactingMutationListener", function () { + var mockTopic, + mockMutationTopic, + mockTransactionService, + mockDomainObject, + mockPersistence; + + beforeEach(function () { + mockTopic = jasmine.createSpy('topic'); + mockMutationTopic = + jasmine.createSpyObj('mutation', ['listen']); + mockTransactionService = + jasmine.createSpyObj('transactionService', [ + 'isActive', + 'startTransaction', + 'addToTransaction', + 'commit' + ]); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getCapability', 'getModel'] + ); + mockPersistence = jasmine.createSpyObj( + 'persistence', + ['persist', 'refresh', 'persisted'] + ); + + mockTopic.andCallFake(function (t) { + return (t === 'mutation') && mockMutationTopic; + }); + + mockDomainObject.getCapability.andCallFake(function (c) { + return (c === 'persistence') && mockPersistence; + }); + + mockPersistence.persisted.andReturn(true); + + return new TransactingMutationListener( + mockTopic, + mockTransactionService + ); + }); + + it("listens for mutation", function () { + expect(mockMutationTopic.listen) + .toHaveBeenCalledWith(jasmine.any(Function)); + }); + + [false, true].forEach(function (isActive) { + var verb = isActive ? "is" : "isn't"; + + function onlyWhenInactive(expectation) { + return isActive ? expectation.not : expectation; + } + + describe("when a transaction " + verb + " active", function () { + var innerVerb = isActive ? "does" : "doesn't"; + + beforeEach(function () { + mockTransactionService.isActive.andReturn(isActive); + }); + + describe("and mutation occurs", function () { + beforeEach(function () { + mockMutationTopic.listen.mostRecentCall + .args[0](mockDomainObject); + }); + + + it(innerVerb + " start a new transaction", function () { + onlyWhenInactive( + expect(mockTransactionService.startTransaction) + ).toHaveBeenCalled(); + }); + + it("adds to the active transaction", function () { + expect(mockTransactionService.addToTransaction) + .toHaveBeenCalledWith( + jasmine.any(Function), + jasmine.any(Function) + ); + }); + + it(innerVerb + " immediately commit", function () { + onlyWhenInactive( + expect(mockTransactionService.commit) + ).toHaveBeenCalled(); + }); + }); + }); + }); + }); + } +); diff --git a/platform/entanglement/src/capabilities/LocationCapability.js b/platform/entanglement/src/capabilities/LocationCapability.js index d1fe61e4c4..0e1c171d89 100644 --- a/platform/entanglement/src/capabilities/LocationCapability.js +++ b/platform/entanglement/src/capabilities/LocationCapability.js @@ -76,17 +76,12 @@ define( * completes. */ LocationCapability.prototype.setPrimaryLocation = function (location) { - var capability = this; return this.domainObject.useCapability( 'mutation', function (model) { model.location = location; } - ).then(function () { - return capability.domainObject - .getCapability('persistence') - .persist(); - }); + ); }; /** diff --git a/platform/entanglement/src/services/LinkService.js b/platform/entanglement/src/services/LinkService.js index d94e362841..74c985f312 100644 --- a/platform/entanglement/src/services/LinkService.js +++ b/platform/entanglement/src/services/LinkService.js @@ -63,14 +63,7 @@ define( ); } - return parentObject.getCapability('composition').add(object) - .then(function (objectInNewContext) { - return parentObject.getCapability('persistence') - .persist() - .then(function () { - return objectInNewContext; - }); - }); + return parentObject.getCapability('composition').add(object); }; return LinkService; diff --git a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js b/platform/entanglement/test/capabilities/LocationCapabilitySpec.js index f9d4aa8fc0..4ff2acd1fd 100644 --- a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js +++ b/platform/entanglement/test/capabilities/LocationCapabilitySpec.js @@ -33,7 +33,6 @@ define( describe("instantiated with domain object", function () { var locationCapability, - persistencePromise, mutationPromise, mockQ, mockInjector, @@ -49,10 +48,6 @@ define( return domainObjectFactory({id: 'root'}); } }, - persistence: jasmine.createSpyObj( - 'persistenceCapability', - ['persist'] - ), mutation: jasmine.createSpyObj( 'mutationCapability', ['invoke'] @@ -65,11 +60,6 @@ define( mockObjectService = jasmine.createSpyObj("objectService", ["getObjects"]); - persistencePromise = new ControlledPromise(); - domainObject.capabilities.persistence.persist.andReturn( - persistencePromise - ); - mutationPromise = new ControlledPromise(); domainObject.capabilities.mutation.invoke.andCallFake( function (mutator) { @@ -103,22 +93,17 @@ define( expect(locationCapability.isOriginal()).toBe(false); }); - it("can persist location", function () { - var persistResult = locationCapability + it("can mutate location", function () { + var result = locationCapability .setPrimaryLocation('root'), whenComplete = jasmine.createSpy('whenComplete'); - persistResult.then(whenComplete); + result.then(whenComplete); expect(domainObject.model.location).not.toBeDefined(); mutationPromise.resolve(); expect(domainObject.model.location).toBe('root'); - expect(whenComplete).not.toHaveBeenCalled(); - expect(domainObject.capabilities.persistence.persist) - .toHaveBeenCalled(); - - persistencePromise.resolve(); expect(whenComplete).toHaveBeenCalled(); }); diff --git a/platform/entanglement/test/services/LinkServiceSpec.js b/platform/entanglement/test/services/LinkServiceSpec.js index 632ab9bb8b..16d82e6601 100644 --- a/platform/entanglement/test/services/LinkServiceSpec.js +++ b/platform/entanglement/test/services/LinkServiceSpec.js @@ -139,20 +139,12 @@ define( parentModel, parentObject, compositionPromise, - persistencePromise, addPromise, - compositionCapability, - persistenceCapability; + compositionCapability; beforeEach(function () { compositionPromise = new ControlledPromise(); - persistencePromise = new ControlledPromise(); addPromise = new ControlledPromise(); - persistenceCapability = jasmine.createSpyObj( - 'persistenceCapability', - ['persist'] - ); - persistenceCapability.persist.andReturn(persistencePromise); compositionCapability = jasmine.createSpyObj( 'compositionCapability', ['invoke', 'add'] @@ -172,7 +164,6 @@ define( return new ControlledPromise(); } }, - persistence: persistenceCapability, composition: compositionCapability } }); @@ -197,15 +188,6 @@ define( .toHaveBeenCalledWith(object); }); - it("persists parent", function () { - linkService.perform(object, parentObject); - expect(addPromise.then).toHaveBeenCalled(); - addPromise.resolve(linkedObject); - expect(parentObject.getCapability) - .toHaveBeenCalledWith('persistence'); - expect(persistenceCapability.persist).toHaveBeenCalled(); - }); - it("returns object representing new link", function () { var returnPromise, whenComplete; returnPromise = linkService.perform(object, parentObject); @@ -213,7 +195,6 @@ define( returnPromise.then(whenComplete); addPromise.resolve(linkedObject); - persistencePromise.resolve(); compositionPromise.resolve([linkedObject]); expect(whenComplete).toHaveBeenCalledWith(linkedObject); }); diff --git a/platform/features/clock/src/actions/AbstractStartTimerAction.js b/platform/features/clock/src/actions/AbstractStartTimerAction.js index 50154dfc31..dc55f446f4 100644 --- a/platform/features/clock/src/actions/AbstractStartTimerAction.js +++ b/platform/features/clock/src/actions/AbstractStartTimerAction.js @@ -49,17 +49,11 @@ define( var domainObject = this.domainObject, now = this.now; - function doPersist() { - var persistence = domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - function setTimestamp(model) { model.timestamp = now(); } - return domainObject.useCapability('mutation', setTimestamp) - .then(doPersist); + return domainObject.useCapability('mutation', setTimestamp); }; return AbstractStartTimerAction; diff --git a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js b/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js index a7c6150c87..6478d07877 100644 --- a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js +++ b/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's start/restart action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, action; @@ -45,14 +44,7 @@ define( 'domainObject', ['getCapability', 'useCapability'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -67,18 +59,16 @@ define( }); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("does not truncate milliseconds", function () { mockNow.andReturn(42321); action.perform(); expect(testModel.timestamp).toEqual(42321); - expect(mockPersistence.persist).toHaveBeenCalled(); }); }); } diff --git a/platform/features/clock/test/actions/RestartTimerActionSpec.js b/platform/features/clock/test/actions/RestartTimerActionSpec.js index af6e8a0548..c0d4ded90d 100644 --- a/platform/features/clock/test/actions/RestartTimerActionSpec.js +++ b/platform/features/clock/test/actions/RestartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's restart action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, testContext, action; @@ -46,14 +45,7 @@ define( 'domainObject', ['getCapability', 'useCapability', 'getModel'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -70,11 +62,10 @@ define( action = new RestartTimerAction(mockNow, testContext); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("applies only to timers with a target time", function () { diff --git a/platform/features/clock/test/actions/StartTimerActionSpec.js b/platform/features/clock/test/actions/StartTimerActionSpec.js index 58d66e5cb0..588f776d70 100644 --- a/platform/features/clock/test/actions/StartTimerActionSpec.js +++ b/platform/features/clock/test/actions/StartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's start action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, testContext, action; @@ -46,14 +45,7 @@ define( 'domainObject', ['getCapability', 'useCapability', 'getModel'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -70,11 +62,10 @@ define( action = new StartTimerAction(mockNow, testContext); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("applies only to timers without a target time", function () { diff --git a/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js b/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js index 745c3cc5cf..d7bb73c6ec 100644 --- a/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js +++ b/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js @@ -35,7 +35,6 @@ define( */ function TimelineDragHandler(domainObject, objectLoader) { var timespans = {}, - persists = {}, mutations = {}, compositions = {}, dirty = {}; @@ -56,8 +55,6 @@ define( timespans[id] = timespan; // And its mutation capability mutations[id] = object.getCapability('mutation'); - // Also cache the persistence capability for later - persists[id] = object.getCapability('persistence'); // And the composition, for bulk moves compositions[id] = object.getModel().composition || []; }); @@ -71,19 +68,14 @@ define( } // Persist changes for objects by id (when dragging ends) - function doPersist(id) { - var persistence = persists[id], - mutation = mutations[id]; + function finalMutate(id) { + var mutation = mutations[id]; if (mutation) { // Mutate just to update the timestamp (since we // explicitly don't do this during the drag to // avoid firing a ton of refreshes.) mutation.mutate(function () {}); } - if (persistence) { - // Persist the changes - persistence.persist(); - } } // Use the object loader to get objects which have timespans @@ -105,7 +97,7 @@ define( */ persist: function () { // Persist every dirty object... - Object.keys(dirty).forEach(doPersist); + Object.keys(dirty).forEach(finalMutate); // Clear out the dirty list dirty = {}; }, diff --git a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js index b082d4e1c0..bafd5cd71c 100644 --- a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js +++ b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js @@ -35,7 +35,6 @@ define( var domainObject = swimlane && swimlane.domainObject, model = (domainObject && domainObject.getModel()) || {}, mutator = domainObject && domainObject.getCapability('mutation'), - persister = domainObject && domainObject.getCapability('persistence'), type = domainObject && domainObject.getCapability('type'), dropHandler = new TimelineSwimlaneDropHandler(swimlane); @@ -48,7 +47,7 @@ define( mutator.mutate(function (m) { m.relationships = m.relationships || {}; m.relationships[ACTIVITY_RELATIONSHIP] = value; - }).then(persister.persist); + }); } } // ...otherwise, use as a getter @@ -63,7 +62,7 @@ define( // Update the link mutator.mutate(function (m) { m.link = value; - }).then(persister.persist); + }); } return model.link; } @@ -84,7 +83,7 @@ define( } // Activities should have the Activity Modes and Activity Link dialog - if (type && type.instanceOf("activity") && mutator && persister) { + if (type && type.instanceOf("activity") && mutator) { swimlane.modes = modes; swimlane.link = link; } diff --git a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js index affa631863..8aa642f6fb 100644 --- a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js +++ b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js @@ -29,16 +29,6 @@ define( * @constructor */ function TimelineSwimlaneDropHandler(swimlane) { - // Utility function; like $q.when, but synchronous (to reduce - // performance impact when wrapping synchronous values) - function asPromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; - } - // Check if we are in edit mode (also check parents) function inEditMode() { return swimlane.domainObject.hasCapability('editor') && @@ -75,16 +65,7 @@ define( // Initiate mutation of a domain object function doMutate(domainObject, mutator) { - return asPromise( - domainObject.useCapability("mutation", mutator) - ).then(function () { - // Persist the results of mutation - var persistence = domainObject.getCapability("persistence"); - if (persistence) { - // Persist the changes - persistence.persist(); - } - }); + return domainObject.useCapability("mutation", mutator); } // Check if this swimlane is in a state where a drop-after will diff --git a/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js b/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js index 7ea7905971..03bb50504a 100644 --- a/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js +++ b/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js @@ -32,7 +32,6 @@ define( mockDomainObjects, mockTimespans, mockMutations, - mockPersists, mockCallback, handler; @@ -66,7 +65,6 @@ define( mockDomainObj.useCapability.andReturn(asPromise(mockTimespans[id])); mockDomainObj.getCapability.andCallFake(function (c) { return { - persistence: mockPersists[id], mutation: mockMutations[id] }[c]; }); @@ -76,17 +74,12 @@ define( beforeEach(function () { mockTimespans = {}; - mockPersists = {}; mockMutations = {}; ['a', 'b', 'c', 'd', 'e', 'f'].forEach(function (id, index) { mockTimespans[id] = jasmine.createSpyObj( 'timespan-' + id, ['getStart', 'getEnd', 'getDuration', 'setStart', 'setEnd', 'setDuration'] ); - mockPersists[id] = jasmine.createSpyObj( - 'persistence-' + id, - ['persist'] - ); mockMutations[id] = jasmine.createSpyObj( 'mutation-' + id, ['mutate'] @@ -209,20 +202,6 @@ define( expect(mockTimespans.c.setStart).toHaveBeenCalledWith(1000); }); - it("persists mutated objects", function () { - handler.start('a', 20); - handler.end('b', 50); - handler.duration('c', 30); - handler.persist(); - expect(mockPersists.a.persist).toHaveBeenCalled(); - expect(mockPersists.b.persist).toHaveBeenCalled(); - expect(mockPersists.c.persist).toHaveBeenCalled(); - expect(mockPersists.d.persist).not.toHaveBeenCalled(); - expect(mockPersists.e.persist).not.toHaveBeenCalled(); - expect(mockPersists.f.persist).not.toHaveBeenCalled(); - }); - - }); } ); diff --git a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js index fb251c22cc..2d777656ee 100644 --- a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js +++ b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js @@ -50,10 +50,6 @@ define( 'mutation', ['mutate'] ); - mockCapabilities.persistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); mockCapabilities.type = jasmine.createSpyObj( 'type', ['instanceOf'] @@ -115,11 +111,6 @@ define( .toHaveBeenCalledWith(jasmine.any(Function)); mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel); expect(testModel.relationships.modes).toEqual(['abc', 'xyz']); - - // Verify that persistence is called when promise resolves - expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled(); - mockPromise.then.mostRecentCall.args[0](); - expect(mockCapabilities.persistence.persist).toHaveBeenCalled(); }); it("mutates modes when used as a setter", function () { @@ -128,11 +119,6 @@ define( .toHaveBeenCalledWith(jasmine.any(Function)); mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel); expect(testModel.link).toEqual("http://www.noaa.gov"); - - // Verify that persistence is called when promise resolves - expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled(); - mockPromise.then.mostRecentCall.args[0](); - expect(mockCapabilities.persistence.persist).toHaveBeenCalled(); }); it("does not mutate modes when unchanged", function () { diff --git a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js index 6e76385139..1ea986ae16 100644 --- a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js +++ b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js @@ -29,7 +29,6 @@ define( mockOtherObject, mockActionCapability, mockEditorCapability, - mockPersistence, mockContext, mockAction, handler; @@ -76,7 +75,6 @@ define( ["getId", "getCapability", "useCapability", "hasCapability"] ); mockActionCapability = jasmine.createSpyObj("action", ["perform", "getActions"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockContext = jasmine.createSpyObj('context', ['getParent']); mockActionCapability.getActions.andReturn([mockAction]); @@ -89,14 +87,12 @@ define( mockSwimlane.domainObject.getCapability.andCallFake(function (c) { return { action: mockActionCapability, - persistence: mockPersistence, editor: mockEditorCapability }[c]; }); mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) { return { action: mockActionCapability, - persistence: mockPersistence, editor: mockEditorCapability }[c]; }); @@ -162,8 +158,6 @@ define( mockSwimlane.domainObject.useCapability.mostRecentCall .args[1](testModel); expect(testModel.composition).toEqual(['c', 'd']); - // Finally, should also have persisted - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("inserts after as a peer when highlighted at the bottom", function () {