From d0c5731287aa3470399fdede57f0a2570b32f3f3 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Mon, 25 Oct 2021 13:13:17 -0700 Subject: [PATCH] Implement transactions in Object API and retire legacy transactions #4089 (#4195) * Implement transactions in Object API and retire legacy transactions #4089 * Added `objectAPI.refresh` Co-authored-by: Andrew Henry --- platform/commonUI/edit/bundle.js | 36 ---- .../commonUI/edit/src/actions/SaveAsAction.js | 4 +- .../edit/src/capabilities/EditorCapability.js | 43 ---- .../TransactionCapabilityDecorator.js | 75 ------- .../TransactionalPersistenceCapability.js | 91 --------- .../edit/src/services/NestedTransaction.js | 49 ----- .../commonUI/edit/src/services/Transaction.js | 99 --------- .../edit/src/services/TransactionManager.js | 119 ----------- .../edit/src/services/TransactionService.js | 138 ------------- .../test/capabilities/EditorCapabilitySpec.js | 192 ------------------ .../TransactionalPersistenceCapabilitySpec.js | 111 ---------- .../test/services/NestedTransactionSpec.js | 75 ------- .../test/services/TransactionManagerSpec.js | 141 ------------- .../test/services/TransactionServiceSpec.js | 139 ------------- .../edit/test/services/TransactionSpec.js | 109 ---------- platform/core/bundle.js | 8 - .../src/runs/TransactingMutationListener.js | 55 ----- .../runs/TransactingMutationListenerSpec.js | 112 ---------- src/api/Editor.js | 46 ++--- src/api/objects/MutableDomainObject.js | 4 +- src/api/objects/ObjectAPI.js | 58 +++++- src/api/objects/ObjectAPISpec.js | 26 +++ .../api/objects/Transaction.js | 71 ++++--- src/api/objects/object-utils.js | 9 +- src/plugins/notebook/pluginSpec.js | 4 + src/plugins/notebook/snapshot.js | 3 +- .../notebook/utils/notebook-entriesSpec.js | 7 + 27 files changed, 170 insertions(+), 1654 deletions(-) delete mode 100644 platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js delete mode 100644 platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js delete mode 100644 platform/commonUI/edit/src/services/NestedTransaction.js delete mode 100644 platform/commonUI/edit/src/services/Transaction.js delete mode 100644 platform/commonUI/edit/src/services/TransactionManager.js delete mode 100644 platform/commonUI/edit/src/services/TransactionService.js delete mode 100644 platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js delete mode 100644 platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js delete mode 100644 platform/commonUI/edit/test/services/NestedTransactionSpec.js delete mode 100644 platform/commonUI/edit/test/services/TransactionManagerSpec.js delete mode 100644 platform/commonUI/edit/test/services/TransactionServiceSpec.js delete mode 100644 platform/commonUI/edit/test/services/TransactionSpec.js delete mode 100644 platform/core/src/runs/TransactingMutationListener.js delete mode 100644 platform/core/test/runs/TransactingMutationListenerSpec.js rename platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js => src/api/objects/Transaction.js (50%) diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 0280679b6a..4482231816 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -34,9 +34,6 @@ define([ "./src/policies/EditPersistableObjectsPolicy", "./src/representers/EditRepresenter", "./src/capabilities/EditorCapability", - "./src/capabilities/TransactionCapabilityDecorator", - "./src/services/TransactionManager", - "./src/services/TransactionService", "./src/creation/CreateMenuController", "./src/creation/LocatorController", "./src/creation/CreationPolicy", @@ -63,9 +60,6 @@ define([ EditPersistableObjectsPolicy, EditRepresenter, EditorCapability, - TransactionCapabilityDecorator, - TransactionManager, - TransactionService, CreateMenuController, LocatorController, CreationPolicy, @@ -263,26 +257,6 @@ define([ } ], "components": [ - { - "type": "decorator", - "provides": "capabilityService", - "implementation": TransactionCapabilityDecorator, - "depends": [ - "$q", - "transactionManager" - ], - "priority": "fallback" - }, - { - "type": "provider", - "provides": "transactionService", - "implementation": TransactionService, - "depends": [ - "$q", - "$log", - "cacheService" - ] - }, { "key": "CreateActionProvider", "provides": "actionService", @@ -320,7 +294,6 @@ define([ "description": "Provides transactional editing capabilities", "implementation": EditorCapability, "depends": [ - "transactionService", "openmct" ] } @@ -331,15 +304,6 @@ define([ "template": locatorTemplate } ], - "services": [ - { - "key": "transactionManager", - "implementation": TransactionManager, - "depends": [ - "transactionService" - ] - } - ], "runs": [ { depends: [ diff --git a/platform/commonUI/edit/src/actions/SaveAsAction.js b/platform/commonUI/edit/src/actions/SaveAsAction.js index 64cc990200..cafc08b3cd 100644 --- a/platform/commonUI/edit/src/actions/SaveAsAction.js +++ b/platform/commonUI/edit/src/actions/SaveAsAction.js @@ -155,8 +155,8 @@ function ( } function undirtyOriginals(object) { - return Promise.all(toUndirty.map(undirty)) - .then(() => { + return object.getCapability("persistence").persist() + .then(function () { return object; }); } diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js index 1141e5dae3..db3970c14e 100644 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditorCapability.js @@ -30,32 +30,17 @@ define( * Once initiated, any persist operations will be queued pending a * subsequent call to [.save()](@link #save) or [.finish()](@link * #finish). - * @param transactionService * @param domainObject * @constructor */ function EditorCapability( - transactionService, openmct, domainObject ) { - this.transactionService = transactionService; this.openmct = openmct; this.domainObject = domainObject; } - /** - * Initiate an editing session. This will start a transaction during - * which any persist operations will be deferred until either save() - * or finish() are called. - */ - EditorCapability.prototype.edit = function () { - if (!this.openmct.editor.isEditing()) { - this.openmct.editor.edit(); - this.domainObject.getCapability('status').set('editing', true); - } - }; - /** * Determines whether this object, or any of its ancestors are * currently being edited. @@ -74,34 +59,6 @@ define( return this.openmct.editor.isEditing(); }; - /** - * Save any unsaved changes from this editing session. This will - * end the current transaction and continue with a new one. - * @returns {*} - */ - EditorCapability.prototype.save = function () { - return Promise.resolve(); - }; - - EditorCapability.prototype.invoke = EditorCapability.prototype.edit; - - /** - * Finish the current editing session. This will discard any pending - * persist operations - * @returns {*} - */ - EditorCapability.prototype.finish = function () { - return Promise.resolve(); - }; - - /** - * @returns {boolean} true if there have been any domain model - * modifications since the last persist, false otherwise. - */ - EditorCapability.prototype.dirty = function () { - return this.transactionService.size() > 0; - }; - return EditorCapability; } ); diff --git a/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js b/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js deleted file mode 100644 index 46eb5ef97f..0000000000 --- a/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js +++ /dev/null @@ -1,75 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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( - ['./TransactionalPersistenceCapability'], - function (TransactionalPersistenceCapability) { - - /** - * Wraps the [PersistenceCapability]{@link PersistenceCapability} with - * transactional capabilities. - * @param $q - * @param transactionService - * @param capabilityService - * @see TransactionalPersistenceCapability - * @constructor - */ - function TransactionCapabilityDecorator( - $q, - transactionService, - capabilityService - ) { - this.capabilityService = capabilityService; - this.transactionService = transactionService; - this.$q = $q; - } - - /** - * Decorate PersistenceCapability to queue persistence calls when a - * transaction is in progress. - */ - TransactionCapabilityDecorator.prototype.getCapabilities = function () { - var self = this, - capabilities = this.capabilityService.getCapabilities - .apply(this.capabilityService, arguments), - persistenceCapability = capabilities.persistence; - - capabilities.persistence = function (domainObject) { - var original = - (typeof persistenceCapability === 'function') - ? persistenceCapability(domainObject) - : persistenceCapability; - - return new TransactionalPersistenceCapability( - self.$q, - self.transactionService, - original, - domainObject - ); - }; - - return capabilities; - }; - - return TransactionCapabilityDecorator; - } -); diff --git a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js deleted file mode 100644 index aa3d04d3ea..0000000000 --- a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js +++ /dev/null @@ -1,91 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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 () { - - /** - * Wraps persistence capability to enable transactions. Transactions - * will cause persist calls not to be invoked immediately, but - * rather queued until [EditorCapability.save()]{@link EditorCapability#save} - * or [EditorCapability.cancel()]{@link EditorCapability#cancel} are - * called. - * @memberof platform/commonUI/edit/capabilities - * @param $q - * @param transactionManager - * @param persistenceCapability - * @param domainObject - * @constructor - */ - function TransactionalPersistenceCapability( - $q, - transactionManager, - persistenceCapability, - domainObject - ) { - this.transactionManager = transactionManager; - this.persistenceCapability = persistenceCapability; - this.domainObject = domainObject; - this.$q = $q; - } - - /** - * The wrapped persist function. If a transaction is active, persist - * will be queued until the transaction is committed or cancelled. - * @returns {*} - */ - TransactionalPersistenceCapability.prototype.persist = function () { - var wrappedPersistence = this.persistenceCapability; - - if (this.transactionManager.isActive()) { - this.transactionManager.addToTransaction( - this.domainObject.getId(), - wrappedPersistence.persist.bind(wrappedPersistence), - wrappedPersistence.refresh.bind(wrappedPersistence) - ); - - //Need to return a promise from this function - return this.$q.when(true); - } else { - return this.persistenceCapability.persist(); - } - }; - - TransactionalPersistenceCapability.prototype.refresh = function () { - this.transactionManager - .clearTransactionsFor(this.domainObject.getId()); - - return this.persistenceCapability.refresh(); - }; - - TransactionalPersistenceCapability.prototype.getSpace = function () { - return this.persistenceCapability.getSpace(); - }; - - TransactionalPersistenceCapability.prototype.persisted = function () { - return this.persistenceCapability.persisted(); - }; - - return TransactionalPersistenceCapability; - } -); diff --git a/platform/commonUI/edit/src/services/NestedTransaction.js b/platform/commonUI/edit/src/services/NestedTransaction.js deleted file mode 100644 index 91d4371efe..0000000000 --- a/platform/commonUI/edit/src/services/NestedTransaction.js +++ /dev/null @@ -1,49 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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 deleted file mode 100644 index b4442147b9..0000000000 --- a/platform/commonUI/edit/src/services/Transaction.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/TransactionManager.js b/platform/commonUI/edit/src/services/TransactionManager.js deleted file mode 100644 index 52887a83cd..0000000000 --- a/platform/commonUI/edit/src/services/TransactionManager.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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 () { - /** - * Manages transactions to support the TransactionalPersistenceCapability. - * This assumes that all commit/cancel callbacks for a given domain - * object are equivalent, and only need to be added once to any active - * transaction. Violating this assumption may cause unexpected behavior. - * @constructor - * @memberof platform/commonUI/edit - */ - function TransactionManager(transactionService) { - this.transactionService = transactionService; - this.clearTransactionFns = {}; - } - - /** - * Check if a transaction is currently active. - * @returns {boolean} true if there is a transaction active - */ - TransactionManager.prototype.isActive = function () { - return this.transactionService.isActive(); - }; - - /** - * Check if callbacks associated with this domain object have already - * been added to the active transaction. - * @private - * @param {string} id the identifier of the domain object to check - * @returns {boolean} true if callbacks have been added - */ - TransactionManager.prototype.isScheduled = function (id) { - return Boolean(this.clearTransactionFns[id]); - }; - - /** - * Add callbacks associated with this domain object to the active - * transaction. Both callbacks are expected to return promises that - * resolve when their associated behavior is complete. - * - * If callbacks associated with this domain object have already been - * added to the active transaction, this call will be ignored. - * - * @param {string} id the identifier of the associated domain object - * @param {Function} onCommit behavior to invoke when committing transaction - * @param {Function} onCancel behavior to invoke when cancelling transaction - */ - TransactionManager.prototype.addToTransaction = function ( - id, - onCommit, - onCancel - ) { - var release = this.releaseClearFn.bind(this, id); - - function chain(promiseFn, nextFn) { - return function () { - return promiseFn().then(nextFn); - }; - } - - /** - * Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence - * call is executed. This should prevent stale objects being persisted and overwriting fresh ones. - */ - if (this.isScheduled(id)) { - this.clearTransactionsFor(id); - } - - this.clearTransactionFns[id] = - this.transactionService.addToTransaction( - chain(onCommit, release), - chain(onCancel, release) - ); - }; - - /** - * Remove any callbacks associated with this domain object from the - * active transaction. - * @param {string} id the identifier for the domain object - */ - TransactionManager.prototype.clearTransactionsFor = function (id) { - if (this.isScheduled(id)) { - this.clearTransactionFns[id](); - this.releaseClearFn(id); - } - }; - - /** - * Release the cached "remove from transaction" function that has been - * stored in association with this domain object. - * @param {string} id the identifier for the domain object - * @private - */ - TransactionManager.prototype.releaseClearFn = function (id) { - delete this.clearTransactionFns[id]; - }; - - return TransactionManager; -}); diff --git a/platform/commonUI/edit/src/services/TransactionService.js b/platform/commonUI/edit/src/services/TransactionService.js deleted file mode 100644 index dbf7abaceb..0000000000 --- a/platform/commonUI/edit/src/services/TransactionService.js +++ /dev/null @@ -1,138 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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', './NestedTransaction'], - function (Transaction, NestedTransaction) { - /** - * Implements an application-wide transaction state. Once a - * transaction is started, calls to - * [PersistenceCapability.persist()]{@link PersistenceCapability#persist} - * will be deferred until a subsequent call to - * [TransactionService.commit]{@link TransactionService#commit} is made. - * - * @memberof platform/commonUI/edit/services - * @param $q - * @constructor - */ - function TransactionService($q, $log, cacheService) { - this.$q = $q; - this.$log = $log; - this.cacheService = cacheService; - this.transactions = []; - } - - /** - * Starts a transaction. While a transaction is active all calls to - * [PersistenceCapability.persist](@link PersistenceCapability#persist) - * will be queued until [commit]{@link #commit} or [cancel]{@link - * #cancel} are called - */ - TransactionService.prototype.startTransaction = function () { - 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.transactions.length > 0; - }; - - /** - * Adds provided functions to a queue to be called on - * [.commit()]{@link #commit} or - * [.cancel()]{@link #commit} - * @param onCommit A function to call on commit - * @param onCancel A function to call on cancel - */ - TransactionService.prototype.addToTransaction = function (onCommit, 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"); - } - }; - - /** - * Get the transaction at the top of the stack. - * @private - */ - TransactionService.prototype.activeTransaction = function () { - return this.transactions[this.transactions.length - 1]; - }; - - /** - * All persist calls deferred since the beginning of the transaction - * will be committed. If this is the last transaction, clears the - * cache. - * - * @returns {Promise} resolved when all persist operations have - * completed. Will reject if any commit operations fail - */ - TransactionService.prototype.commit = function () { - var transaction = this.transactions.pop(); - if (!transaction) { - return Promise.reject(); - } - - if (!this.isActive()) { - return transaction.commit() - .then(function (r) { - this.cacheService.flush(); - - return r; - }.bind(this)); - } - - return transaction.commit(); - }; - - /** - * Cancel the current transaction, replacing any dirty objects from - * persistence. Not a true rollback, as it cannot be used to undo any - * persist calls that were successful in the event one of a batch of - * persists failing. - * - * @returns {*} - */ - TransactionService.prototype.cancel = function () { - 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.isActive() ? this.activeTransaction().size() : 0; - }; - - return TransactionService; - }); diff --git a/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js deleted file mode 100644 index e97e8a6d8e..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js +++ /dev/null @@ -1,192 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/capabilities/EditorCapability"], - function (EditorCapability) { - - xdescribe("The editor capability", function () { - var mockDomainObject, - capabilities, - mockParentObject, - mockTransactionService, - mockStatusCapability, - mockParentStatus, - mockContextCapability, - capability; - - function fastPromise(val) { - return { - then: function (callback) { - return callback(val); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "hasCapability", "getCapability", "useCapability"] - ); - mockParentObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "hasCapability", "getCapability", "useCapability"] - ); - mockTransactionService = jasmine.createSpyObj( - "transactionService", - [ - "startTransaction", - "size", - "commit", - "cancel" - ] - ); - mockTransactionService.commit.and.returnValue(fastPromise()); - mockTransactionService.cancel.and.returnValue(fastPromise()); - mockTransactionService.isActive = jasmine.createSpy('isActive'); - - mockStatusCapability = jasmine.createSpyObj( - "statusCapability", - ["get", "set"] - ); - mockParentStatus = jasmine.createSpyObj( - "statusCapability", - ["get", "set"] - ); - mockContextCapability = jasmine.createSpyObj( - "contextCapability", - ["getParent"] - ); - mockContextCapability.getParent.and.returnValue(mockParentObject); - - capabilities = { - context: mockContextCapability, - status: mockStatusCapability - }; - - mockDomainObject.hasCapability.and.callFake(function (name) { - return capabilities[name] !== undefined; - }); - - mockDomainObject.getCapability.and.callFake(function (name) { - return capabilities[name]; - }); - - mockParentObject.getCapability.and.returnValue(mockParentStatus); - mockParentObject.hasCapability.and.returnValue(false); - - capability = new EditorCapability( - mockTransactionService, - mockDomainObject - ); - }); - - it("starts a transaction when edit is invoked", function () { - capability.edit(); - expect(mockTransactionService.startTransaction).toHaveBeenCalled(); - }); - - it("sets editing status on object", function () { - capability.edit(); - expect(mockStatusCapability.set).toHaveBeenCalledWith("editing", true); - }); - - it("uses editing status to determine editing context root", function () { - capability.edit(); - mockStatusCapability.get.and.returnValue(false); - expect(capability.isEditContextRoot()).toBe(false); - mockStatusCapability.get.and.returnValue(true); - expect(capability.isEditContextRoot()).toBe(true); - }); - - it("inEditingContext returns true if parent object is being" - + " edited", function () { - mockStatusCapability.get.and.returnValue(false); - mockParentStatus.get.and.returnValue(false); - expect(capability.inEditContext()).toBe(false); - mockParentStatus.get.and.returnValue(true); - expect(capability.inEditContext()).toBe(true); - }); - - describe("save", function () { - beforeEach(function () { - capability.edit(); - capability.save(); - }); - it("commits the transaction", function () { - expect(mockTransactionService.commit).toHaveBeenCalled(); - }); - it("begins a new transaction", function () { - expect(mockTransactionService.startTransaction).toHaveBeenCalled(); - }); - }); - - describe("finish", function () { - beforeEach(function () { - mockTransactionService.isActive.and.returnValue(true); - capability.edit(); - capability.finish(); - }); - it("cancels the transaction", function () { - expect(mockTransactionService.cancel).toHaveBeenCalled(); - }); - it("resets the edit state", function () { - expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false); - }); - }); - - describe("finish", function () { - beforeEach(function () { - mockTransactionService.isActive.and.returnValue(false); - capability.edit(); - }); - - it("does not cancel transaction when transaction is not active", function () { - capability.finish(); - expect(mockTransactionService.cancel).not.toHaveBeenCalled(); - }); - - it("returns a promise", function () { - expect(capability.finish() instanceof Promise).toBe(true); - }); - - }); - - describe("dirty", function () { - var model = {}; - - beforeEach(function () { - mockDomainObject.getModel.and.returnValue(model); - capability.edit(); - capability.finish(); - }); - it("returns true if the object has been modified since it" - + " was last persisted", function () { - mockTransactionService.size.and.returnValue(0); - expect(capability.dirty()).toBe(false); - mockTransactionService.size.and.returnValue(1); - expect(capability.dirty()).toBe(true); - }); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js deleted file mode 100644 index bb38001193..0000000000 --- a/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js +++ /dev/null @@ -1,111 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/capabilities/TransactionalPersistenceCapability" - ], - function (TransactionalPersistenceCapability) { - - function fastPromise(val) { - return { - then: function (callback) { - return callback(val); - } - }; - } - - describe("The transactional persistence decorator", function () { - var mockQ, - mockTransactionManager, - mockPersistence, - mockDomainObject, - testId, - capability; - - beforeEach(function () { - testId = "test-id"; - - mockQ = jasmine.createSpyObj("$q", ["when"]); - mockQ.when.and.callFake(function (val) { - return fastPromise(val); - }); - mockTransactionManager = jasmine.createSpyObj( - "transactionService", - ["isActive", "addToTransaction", "clearTransactionsFor"] - ); - mockPersistence = jasmine.createSpyObj( - "persistenceCapability", - ["persist", "refresh", "getSpace"] - ); - mockPersistence.persist.and.returnValue(fastPromise()); - mockPersistence.refresh.and.returnValue(fastPromise()); - - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getModel", "getId"] - ); - mockDomainObject.getModel.and.returnValue({persisted: 1}); - mockDomainObject.getId.and.returnValue(testId); - - capability = new TransactionalPersistenceCapability( - mockQ, - mockTransactionManager, - mockPersistence, - mockDomainObject - ); - }); - - it("if no transaction is active, passes through to persistence" - + " provider", function () { - mockTransactionManager.isActive.and.returnValue(false); - capability.persist(); - expect(mockPersistence.persist).toHaveBeenCalled(); - }); - - it("if transaction is active, persist and cancel calls are" - + " queued", function () { - mockTransactionManager.isActive.and.returnValue(true); - capability.persist(); - expect(mockTransactionManager.addToTransaction).toHaveBeenCalled(); - mockTransactionManager.addToTransaction.calls.mostRecent().args[1](); - expect(mockPersistence.persist).toHaveBeenCalled(); - mockTransactionManager.addToTransaction.calls.mostRecent().args[2](); - expect(mockPersistence.refresh).toHaveBeenCalled(); - }); - - it("wraps getSpace", function () { - mockPersistence.getSpace.and.returnValue('foo'); - expect(capability.getSpace()).toEqual('foo'); - }); - - it("clears transactions and delegates refresh calls", function () { - capability.refresh(); - expect(mockTransactionManager.clearTransactionsFor) - .toHaveBeenCalledWith(testId); - expect(mockPersistence.refresh) - .toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/edit/test/services/NestedTransactionSpec.js b/platform/commonUI/edit/test/services/NestedTransactionSpec.js deleted file mode 100644 index d5a22604a9..0000000000 --- a/platform/commonUI/edit/test/services/NestedTransactionSpec.js +++ /dev/null @@ -1,75 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/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; - - beforeEach(function () { - mockCommit = jasmine.createSpy('commit'); - mockCancel = jasmine.createSpy('cancel'); - 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/TransactionManagerSpec.js b/platform/commonUI/edit/test/services/TransactionManagerSpec.js deleted file mode 100644 index 9b6e164b9d..0000000000 --- a/platform/commonUI/edit/test/services/TransactionManagerSpec.js +++ /dev/null @@ -1,141 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/services/TransactionManager"], - function (TransactionManager) { - describe("TransactionManager", function () { - var mockTransactionService, - testId, - mockOnCommit, - mockOnCancel, - mockRemoves, - mockPromise, - manager; - - beforeEach(function () { - mockRemoves = []; - mockTransactionService = jasmine.createSpyObj( - "transactionService", - ["addToTransaction", "isActive"] - ); - mockOnCommit = jasmine.createSpy('commit'); - mockOnCancel = jasmine.createSpy('cancel'); - testId = 'test-id'; - mockPromise = jasmine.createSpyObj('promise', ['then']); - - mockOnCommit.and.returnValue(mockPromise); - mockOnCancel.and.returnValue(mockPromise); - - mockTransactionService.addToTransaction.and.callFake(function () { - var mockRemove = - jasmine.createSpy('remove-' + mockRemoves.length); - mockRemoves.push(mockRemove); - - return mockRemove; - }); - - manager = new TransactionManager(mockTransactionService); - }); - - it("delegates isActive calls", function () { - [false, true].forEach(function (state) { - mockTransactionService.isActive.and.returnValue(state); - expect(manager.isActive()).toBe(state); - }); - }); - - describe("when addToTransaction is called", function () { - beforeEach(function () { - manager.addToTransaction( - testId, - mockOnCommit, - mockOnCancel - ); - }); - - it("adds callbacks to the active transaction", function () { - expect(mockTransactionService.addToTransaction) - .toHaveBeenCalledWith( - jasmine.any(Function), - jasmine.any(Function) - ); - }); - - it("invokes passed-in callbacks from its own callbacks", function () { - expect(mockOnCommit).not.toHaveBeenCalled(); - mockTransactionService.addToTransaction - .calls.mostRecent().args[0](); - expect(mockOnCommit).toHaveBeenCalled(); - - expect(mockOnCancel).not.toHaveBeenCalled(); - mockTransactionService.addToTransaction - .calls.mostRecent().args[1](); - expect(mockOnCancel).toHaveBeenCalled(); - }); - - describe("Adds callbacks to transaction", function () { - beforeEach(function () { - spyOn(manager, 'clearTransactionsFor'); - manager.clearTransactionsFor.and.callThrough(); - }); - - it("and clears pending calls if same object", function () { - manager.addToTransaction( - testId, - jasmine.createSpy(), - jasmine.createSpy() - ); - expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId); - }); - - it("and does not clear pending calls if different object", function () { - manager.addToTransaction( - 'other-id', - jasmine.createSpy(), - jasmine.createSpy() - ); - expect(manager.clearTransactionsFor).not.toHaveBeenCalled(); - }); - - afterEach(function () { - expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2); - }); - }); - - it("does not remove callbacks from the transaction", function () { - expect(mockRemoves[0]).not.toHaveBeenCalled(); - }); - - describe("and clearTransactionsFor is subsequently called", function () { - beforeEach(function () { - manager.clearTransactionsFor(testId); - }); - - it("removes callbacks from the transaction", function () { - expect(mockRemoves[0]).toHaveBeenCalled(); - }); - }); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/services/TransactionServiceSpec.js b/platform/commonUI/edit/test/services/TransactionServiceSpec.js deleted file mode 100644 index 548607e16f..0000000000 --- a/platform/commonUI/edit/test/services/TransactionServiceSpec.js +++ /dev/null @@ -1,139 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/services/TransactionService"], - function (TransactionService) { - - describe("The Transaction Service", function () { - var mockQ, - mockLog, - mockCacheService, - transactionService; - - function fastPromise(val) { - return { - then: function (callback) { - return fastPromise(callback(val)); - } - }; - } - - beforeEach(function () { - mockQ = jasmine.createSpyObj("$q", ["all"]); - mockCacheService = jasmine.createSpyObj("cacheService", ["flush"]); - mockQ.all.and.returnValue(fastPromise()); - mockLog = jasmine.createSpyObj("$log", ["error"]); - transactionService = new TransactionService(mockQ, mockLog, mockCacheService); - }); - - it("isActive returns true if a transaction is in progress", function () { - expect(transactionService.isActive()).toBe(false); - transactionService.startTransaction(); - expect(transactionService.isActive()).toBe(true); - }); - - it("addToTransaction queues onCommit and onCancel functions", function () { - var onCommit = jasmine.createSpy('onCommit'), - onCancel = jasmine.createSpy('onCancel'); - - transactionService.startTransaction(); - transactionService.addToTransaction(onCommit, onCancel); - expect(transactionService.size()).toBe(1); - }); - - it("size function returns size of commit and cancel queues", function () { - var onCommit = jasmine.createSpy('onCommit'), - onCancel = jasmine.createSpy('onCancel'); - - transactionService.startTransaction(); - transactionService.addToTransaction(onCommit, onCancel); - transactionService.addToTransaction(onCommit, onCancel); - transactionService.addToTransaction(onCommit, onCancel); - expect(transactionService.size()).toBe(3); - }); - - describe("commit", function () { - var onCommits; - - beforeEach(function () { - onCommits = [0, 1, 2].map(function (val) { - return jasmine.createSpy("onCommit" + val); - }); - - transactionService.startTransaction(); - onCommits.forEach(transactionService.addToTransaction.bind(transactionService)); - }); - - it("commit calls all queued commit functions", function () { - expect(transactionService.size()).toBe(3); - - return transactionService.commit().then(() => { - onCommits.forEach(function (spy) { - expect(spy).toHaveBeenCalled(); - }); - }); - }); - - it("commit resets active state and clears queues", function () { - return transactionService.commit().then(() => { - expect(transactionService.isActive()).toBe(false); - expect(transactionService.size()).toBe(0); - expect(transactionService.size()).toBe(0); - }); - }); - - }); - - describe("cancel", function () { - var onCancels; - - beforeEach(function () { - onCancels = [0, 1, 2].map(function (val) { - return jasmine.createSpy("onCancel" + val); - }); - - transactionService.startTransaction(); - onCancels.forEach(function (onCancel) { - transactionService.addToTransaction(undefined, onCancel); - }); - }); - - it("cancel calls all queued cancel functions", function () { - expect(transactionService.size()).toBe(3); - transactionService.cancel(); - onCancels.forEach(function (spy) { - expect(spy).toHaveBeenCalled(); - }); - }); - - it("cancel resets active state and clears queues", function () { - transactionService.cancel(); - expect(transactionService.isActive()).toBe(false); - expect(transactionService.size()).toBe(0); - }); - - }); - - }); - } -); diff --git a/platform/commonUI/edit/test/services/TransactionSpec.js b/platform/commonUI/edit/test/services/TransactionSpec.js deleted file mode 100644 index 1082ef3930..0000000000 --- a/platform/commonUI/edit/test/services/TransactionSpec.js +++ /dev/null @@ -1,109 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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/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.and.callFake(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 fddc8f4125..18f8444da1 100644 --- a/platform/core/bundle.js +++ b/platform/core/bundle.js @@ -45,7 +45,6 @@ define([ "./src/capabilities/MutationCapability", "./src/capabilities/DelegationCapability", "./src/capabilities/InstantiationCapability", - "./src/runs/TransactingMutationListener", "./src/services/Now", "./src/services/Throttle", "./src/services/Topic", @@ -75,7 +74,6 @@ define([ MutationCapability, DelegationCapability, InstantiationCapability, - TransactingMutationListener, Now, Throttle, Topic, @@ -363,12 +361,6 @@ define([ ] } ], - "runs": [ - { - "implementation": TransactingMutationListener, - "depends": ["topic", "transactionService", "cacheService"] - } - ], "constants": [ { "key": "PERSISTENCE_SPACE", diff --git a/platform/core/src/runs/TransactingMutationListener.js b/platform/core/src/runs/TransactingMutationListener.js deleted file mode 100644 index 9571c913ab..0000000000 --- a/platform/core/src/runs/TransactingMutationListener.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * 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. - *****************************************************************************/ - -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, - cacheService - ) { - - function hasChanged(domainObject) { - var model = domainObject.getModel(); - - return model.persisted === undefined || model.modified > model.persisted; - } - - var mutationTopic = topic('mutation'); - mutationTopic.listen(function (domainObject) { - var persistence = domainObject.getCapability('persistence'); - cacheService.put(domainObject.getId(), domainObject.getModel()); - - if (hasChanged(domainObject)) { - persistence.persist(); - } - }); - } - - return TransactingMutationListener; -}); diff --git a/platform/core/test/runs/TransactingMutationListenerSpec.js b/platform/core/test/runs/TransactingMutationListenerSpec.js deleted file mode 100644 index 4912acd325..0000000000 --- a/platform/core/test/runs/TransactingMutationListenerSpec.js +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, 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, - mockCacheService, - mockTransactionService, - mockDomainObject, - mockModel, - mockPersistence; - - beforeEach(function () { - mockTopic = jasmine.createSpy('topic'); - mockMutationTopic = - jasmine.createSpyObj('mutation', ['listen']); - mockCacheService = - jasmine.createSpyObj('cacheService', [ - 'put' - ]); - mockTransactionService = - jasmine.createSpyObj('transactionService', [ - 'isActive', - 'startTransaction', - 'commit' - ]); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability', 'getModel'] - ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist', 'refresh', 'persisted'] - ); - - mockTopic.and.callFake(function (t) { - expect(t).toBe('mutation'); - - return mockMutationTopic; - }); - - mockDomainObject.getId.and.returnValue('mockId'); - mockDomainObject.getCapability.and.callFake(function (c) { - expect(c).toBe('persistence'); - - return mockPersistence; - }); - mockModel = {}; - mockDomainObject.getModel.and.returnValue(mockModel); - - mockPersistence.persisted.and.returnValue(true); - - return new TransactingMutationListener( - mockTopic, - mockTransactionService, - mockCacheService - ); - }); - - it("listens for mutation", function () { - expect(mockMutationTopic.listen) - .toHaveBeenCalledWith(jasmine.any(Function)); - }); - - it("calls persist if the model has changed", function () { - mockModel.persisted = Date.now(); - - //Mark the model dirty by setting the mutated date later than the last persisted date. - mockModel.modified = mockModel.persisted + 1; - - mockMutationTopic.listen.calls.mostRecent() - .args[0](mockDomainObject); - - expect(mockPersistence.persist).toHaveBeenCalled(); - }); - - it("does not call persist if the model has not changed", function () { - mockModel.persisted = Date.now(); - - mockModel.modified = mockModel.persisted; - - mockMutationTopic.listen.calls.mostRecent() - .args[0](mockDomainObject); - - expect(mockPersistence.persist).not.toHaveBeenCalled(); - }); - }); - } -); diff --git a/src/api/Editor.js b/src/api/Editor.js index f077d7d820..02c679a161 100644 --- a/src/api/Editor.js +++ b/src/api/Editor.js @@ -34,7 +34,6 @@ export default class Editor extends EventEmitter { * Initiate an editing session. This will start a transaction during * which any persist operations will be deferred until either save() * or finish() are called. - * @private */ edit() { if (this.editing === true) { @@ -42,8 +41,8 @@ export default class Editor extends EventEmitter { } this.editing = true; - this.getTransactionService().startTransaction(); this.emit('isEditing', true); + this.openmct.objects.startTransaction(); } /** @@ -56,41 +55,36 @@ export default class Editor extends EventEmitter { /** * Save any unsaved changes from this editing session. This will * end the current transaction. - * - * @private */ save() { - return this.getTransactionService().commit().then((result) => { - this.editing = false; - this.emit('isEditing', false); + const transaction = this.openmct.objects.getActiveTransaction(); - return result; - }).catch((error) => { - throw error; - }); + return transaction.commit() + .then(() => { + this.editing = false; + this.emit('isEditing', false); + }).catch(error => { + throw error; + }).finally(() => { + this.openmct.objects.endTransaction(); + }); } /** * End the currently active transaction and discard unsaved changes. - * - * @private */ cancel() { - let cancelPromise = this.getTransactionService().cancel(); this.editing = false; this.emit('isEditing', false); - return cancelPromise; - } - - /** - * @private - */ - getTransactionService() { - if (!this.transactionService) { - this.transactionService = this.openmct.$injector.get('transactionService'); - } - - return this.transactionService; + return new Promise((resolve, reject) => { + const transaction = this.openmct.objects.getActiveTransaction(); + transaction.cancel() + .then(resolve) + .catch(reject) + .finally(() => { + this.openmct.objects.endTransaction(); + }); + }); } } diff --git a/src/api/objects/MutableDomainObject.js b/src/api/objects/MutableDomainObject.js index 33ad26e16f..5ed866b7d8 100644 --- a/src/api/objects/MutableDomainObject.js +++ b/src/api/objects/MutableDomainObject.js @@ -129,9 +129,7 @@ class MutableDomainObject { mutable.$observe('$_synchronize_model', (updatedObject) => { let clone = JSON.parse(JSON.stringify(updatedObject)); - let deleted = _.difference(Object.keys(mutable), Object.keys(updatedObject)); - deleted.forEach((propertyName) => delete mutable[propertyName]); - Object.assign(mutable, clone); + utils.refresh(mutable, clone); }); return mutable; diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 588e11c0aa..4dbe2da7e1 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -26,6 +26,7 @@ import RootRegistry from './RootRegistry'; import RootObjectProvider from './RootObjectProvider'; import EventEmitter from 'EventEmitter'; import InterceptorRegistry from './InterceptorRegistry'; +import Transaction from './Transaction'; /** * Utilities for loading, saving, and manipulating domain objects. @@ -34,12 +35,14 @@ import InterceptorRegistry from './InterceptorRegistry'; */ function ObjectAPI(typeRegistry, openmct) { + this.openmct = openmct; + this.typeRegistry = typeRegistry; this.eventEmitter = new EventEmitter(); this.providers = {}; this.rootRegistry = new RootRegistry(); this.injectIdentifierService = function () { - this.identifierService = openmct.$injector.get("identifierService"); + this.identifierService = this.openmct.$injector.get("identifierService"); }; this.rootProvider = new RootObjectProvider(this.rootRegistry); @@ -86,6 +89,14 @@ ObjectAPI.prototype.getProvider = function (identifier) { return this.providers[namespace] || this.fallbackProvider; }; +/** + * Get an active transaction instance + * @returns {Transaction} a transaction object + */ +ObjectAPI.prototype.getActiveTransaction = function () { + return this.transaction; +}; + /** * Get the root-level object. * @returns {Promise.} a promise for the root object @@ -323,6 +334,24 @@ ObjectAPI.prototype.save = function (domainObject) { return result; }; +/** + * After entering into edit mode, creates a new instance of Transaction to keep track of changes in Objects + */ +ObjectAPI.prototype.startTransaction = function () { + if (this.isTransactionActive()) { + throw new Error("Unable to start new Transaction: Previous Transaction is active"); + } + + this.transaction = new Transaction(this); +}; + +/** + * Clear instance of Transaction + */ +ObjectAPI.prototype.endTransaction = function () { + this.transaction = null; +}; + /** * Add a root-level object. * @param {module:openmct.ObjectAPI~Identifier|function} an array of @@ -412,6 +441,12 @@ ObjectAPI.prototype.mutate = function (domainObject, path, value) { //Destroy temporary mutable object this.destroyMutable(mutableDomainObject); } + + if (this.isTransactionActive()) { + this.transaction.add(domainObject); + } else { + this.save(domainObject); + } }; /** @@ -448,6 +483,23 @@ ObjectAPI.prototype._toMutable = function (object) { return mutableObject; }; +/** + * Updates a domain object based on its latest persisted state. Note that this will mutate the provided object. + * @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store + * @returns {Promise} the provided object, updated to reflect the latest persisted state of the object. + */ +ObjectAPI.prototype.refresh = async function (domainObject) { + const refreshedObject = await this.get(domainObject.identifier); + + if (domainObject.isMutable) { + domainObject.$refresh(refreshedObject); + } else { + utils.refresh(domainObject, refreshedObject); + } + + return domainObject; +}; + /** * @param module:openmct.ObjectAPI~Identifier identifier An object identifier * @returns {boolean} true if the object can be mutated, otherwise returns false @@ -524,6 +576,10 @@ ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) { && domainObject.location !== this.makeKeyString(objectPath[1].identifier); }; +ObjectAPI.prototype.isTransactionActive = function () { + return Boolean(this.transaction && this.openmct.editor.isEditing()); +}; + /** * Uniquely identifies a domain object. * diff --git a/src/api/objects/ObjectAPISpec.js b/src/api/objects/ObjectAPISpec.js index 697d61a58c..8c4f6d7830 100644 --- a/src/api/objects/ObjectAPISpec.js +++ b/src/api/objects/ObjectAPISpec.js @@ -26,6 +26,10 @@ describe("The Object API", () => { openmct.$injector.get.and.returnValue(mockIdentifierService); objectAPI = new ObjectAPI(typeRegistry, openmct); + + openmct.editor = {}; + openmct.editor.isEditing = () => false; + mockDomainObject = { identifier: { namespace: TEST_NAMESPACE, @@ -223,6 +227,28 @@ describe("The Object API", () => { expect(testObject.name).toBe(MUTATED_NAME); }); + it('Provides a way of refreshing an object from the persistence store', () => { + const modifiedTestObject = JSON.parse(JSON.stringify(testObject)); + const OTHER_ATTRIBUTE_VALUE = 'Modified value'; + const NEW_ATTRIBUTE_VALUE = 'A new attribute'; + modifiedTestObject.otherAttribute = OTHER_ATTRIBUTE_VALUE; + modifiedTestObject.newAttribute = NEW_ATTRIBUTE_VALUE; + delete modifiedTestObject.objectAttribute; + + spyOn(objectAPI, 'get'); + objectAPI.get.and.returnValue(Promise.resolve(modifiedTestObject)); + + expect(objectAPI.get).not.toHaveBeenCalled(); + + return objectAPI.refresh(testObject).then(() => { + expect(objectAPI.get).toHaveBeenCalledWith(testObject.identifier); + + expect(testObject.otherAttribute).toEqual(OTHER_ATTRIBUTE_VALUE); + expect(testObject.newAttribute).toEqual(NEW_ATTRIBUTE_VALUE); + expect(testObject.objectAttribute).not.toBeDefined(); + }); + }); + describe ('uses a MutableDomainObject', () => { it('and retains properties of original object ', function () { expect(hasOwnProperty(mutable, 'identifier')).toBe(true); diff --git a/platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js b/src/api/objects/Transaction.js similarity index 50% rename from platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js rename to src/api/objects/Transaction.js index e730e67aa3..e9522226bd 100644 --- a/platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js +++ b/src/api/objects/Transaction.js @@ -20,35 +20,52 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define( - [ - "../../src/capabilities/TransactionalPersistenceCapability", - "../../src/capabilities/TransactionCapabilityDecorator" - ], - function (TransactionalPersistenceCapability, TransactionCapabilityDecorator) { +export default class Transaction { + constructor(objectAPI) { + this.dirtyObjects = new Set(); + this.objectAPI = objectAPI; + } - describe("The transaction capability decorator", function () { - var mockQ, - mockTransactionService, - mockCapabilityService, - provider; + add(object) { + this.dirtyObjects.add(object); + } - beforeEach(function () { - mockQ = {}; - mockTransactionService = {}; - mockCapabilityService = jasmine.createSpyObj("capabilityService", ["getCapabilities"]); - mockCapabilityService.getCapabilities.and.returnValue({ - persistence: function () {} + cancel() { + return this._clear(); + } + + commit() { + const promiseArray = []; + const save = this.objectAPI.save.bind(this.objectAPI); + this.dirtyObjects.forEach(object => { + promiseArray.push(this.createDirtyObjectPromise(object, save)); + }); + + return Promise.all(promiseArray); + } + + createDirtyObjectPromise(object, action) { + return new Promise((resolve, reject) => { + action(object) + .then(resolve) + .catch(reject) + .finally(() => { + this.dirtyObjects.delete(object); }); - - provider = new TransactionCapabilityDecorator(mockQ, mockTransactionService, mockCapabilityService); - - }); - it("decorates the persistence capability", function () { - var capabilities = provider.getCapabilities(); - expect(capabilities.persistence({}) instanceof TransactionalPersistenceCapability).toBe(true); - }); - }); } -); + + start() { + this.dirtyObjects = new Set(); + } + + _clear() { + const promiseArray = []; + const refresh = this.objectAPI.refresh.bind(this.objectAPI); + this.dirtyObjects.forEach(object => { + promiseArray.push(this.createDirtyObjectPromise(object, refresh)); + }); + + return Promise.all(promiseArray); + } +} diff --git a/src/api/objects/object-utils.js b/src/api/objects/object-utils.js index 722e06a079..41e556974e 100644 --- a/src/api/objects/object-utils.js +++ b/src/api/objects/object-utils.js @@ -165,12 +165,19 @@ define([ return identifierEquals(a.identifier, b.identifier); } + function refresh(oldObject, newObject) { + let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject)); + deleted.forEach((propertyName) => delete oldObject[propertyName]); + Object.assign(oldObject, newObject); + } + return { toOldFormat: toOldFormat, toNewFormat: toNewFormat, makeKeyString: makeKeyString, parseKeyString: parseKeyString, equals: objectEquals, - identifierEquals: identifierEquals + identifierEquals: identifierEquals, + refresh: refresh }; }); diff --git a/src/plugins/notebook/pluginSpec.js b/src/plugins/notebook/pluginSpec.js index 1becd1cfd5..3b5217b6ae 100644 --- a/src/plugins/notebook/pluginSpec.js +++ b/src/plugins/notebook/pluginSpec.js @@ -148,10 +148,14 @@ describe("Notebook plugin:", () => { 'observe' ]); + openmct.editor = {}; + openmct.editor.isEditing = () => false; + const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]); notebookViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'notebook-vue'); testObjectProvider.get.and.returnValue(Promise.resolve(notebookViewObject)); + testObjectProvider.create.and.returnValue(Promise.resolve(notebookViewObject)); openmct.objects.addProvider('test-namespace', testObjectProvider); testObjectProvider.observe.and.returnValue(() => {}); diff --git a/src/plugins/notebook/snapshot.js b/src/plugins/notebook/snapshot.js index b3762da797..282640f161 100644 --- a/src/plugins/notebook/snapshot.js +++ b/src/plugins/notebook/snapshot.js @@ -110,8 +110,7 @@ export default class Snapshot { } return () => { - const path = window.location.href.split('#'); - window.location.href = path[0] + url; + window.location.href = window.location.origin + url; }; } } diff --git a/src/plugins/notebook/utils/notebook-entriesSpec.js b/src/plugins/notebook/utils/notebook-entriesSpec.js index 8478cdbafe..9fbcab5546 100644 --- a/src/plugins/notebook/utils/notebook-entriesSpec.js +++ b/src/plugins/notebook/utils/notebook-entriesSpec.js @@ -44,6 +44,7 @@ const notebookDomainObject = { namespace: '' }, type: 'notebook', + name: 'Test Notebook', configuration: { defaultSort: 'oldest', entries: notebookEntries, @@ -118,6 +119,12 @@ describe('Notebook Entries:', () => { 'create', 'update' ])); + openmct.editor = { + isEditing: () => false + }; + openmct.objects.isPersistable = () => true; + openmct.objects.save = () => Promise.resolve(true); + window.localStorage.setItem('notebook-storage', null); });