Compare commits
	
		
			15 Commits
		
	
	
		
			tc-nav-abo
			...
			couch-sear
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 343e547baa | ||
|   | ddb2178220 | ||
|   | c0cc5e1db9 | ||
|   | 3ac1cdff5b | ||
|   | 2e7adbb9be | ||
|   | 4be77ce315 | ||
|   | 663efd5715 | ||
|   | 15c8ccea3b | ||
|   | 2f7019e51e | ||
|   | c61610a600 | ||
|   | f718383896 | ||
|   | ad87dd6fdc | ||
|   | 80c833db88 | ||
|   | a756891ccd | ||
|   | 2e1b6c5190 | 
| @@ -1,8 +0,0 @@ | ||||
| # Couch DB Persistence Plugin | ||||
| An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL  | ||||
| for the CouchDB database as a parameter. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct')) | ||||
| ``` | ||||
| @@ -1,78 +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/CouchPersistenceProvider", | ||||
|     "./src/CouchIndicator" | ||||
| ], function ( | ||||
|     CouchPersistenceProvider, | ||||
|     CouchIndicator | ||||
| ) { | ||||
|  | ||||
|     return { | ||||
|         name: "platform/persistence/couch", | ||||
|         definition: { | ||||
|             "name": "Couch Persistence", | ||||
|             "description": "Adapter to read and write objects using a CouchDB instance.", | ||||
|             "extensions": { | ||||
|                 "components": [ | ||||
|                     { | ||||
|                         "provides": "persistenceService", | ||||
|                         "type": "provider", | ||||
|                         "implementation": CouchPersistenceProvider, | ||||
|                         "depends": [ | ||||
|                             "$http", | ||||
|                             "$q", | ||||
|                             "PERSISTENCE_SPACE", | ||||
|                             "COUCHDB_PATH" | ||||
|                         ] | ||||
|                     } | ||||
|                 ], | ||||
|                 "constants": [ | ||||
|                     { | ||||
|                         "key": "PERSISTENCE_SPACE", | ||||
|                         "value": "mct" | ||||
|                     }, | ||||
|                     { | ||||
|                         "key": "COUCHDB_PATH", | ||||
|                         "value": "/couch/openmct" | ||||
|                     }, | ||||
|                     { | ||||
|                         "key": "COUCHDB_INDICATOR_INTERVAL", | ||||
|                         "value": 15000 | ||||
|                     } | ||||
|                 ], | ||||
|                 "indicators": [ | ||||
|                     { | ||||
|                         "implementation": CouchIndicator, | ||||
|                         "depends": [ | ||||
|                             "$http", | ||||
|                             "$interval", | ||||
|                             "COUCHDB_PATH", | ||||
|                             "COUCHDB_INDICATOR_INTERVAL" | ||||
|                         ] | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }); | ||||
| @@ -1,61 +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 CouchDocument describes domain object model in a format | ||||
|          * which is easily read-written to CouchDB. This includes | ||||
|          * Couch's _id and _rev fields, as well as a separate | ||||
|          * metadata field which contains a subset of information found | ||||
|          * in the model itself (to support search optimization with | ||||
|          * CouchDB views.) | ||||
|          * @memberof platform/persistence/couch | ||||
|          * @constructor | ||||
|          * @param {string} id the id under which to store this mode | ||||
|          * @param {object} model the model to store | ||||
|          * @param {string} rev the revision to include (or undefined, | ||||
|          *        if no revision should be noted for couch) | ||||
|          * @param {boolean} whether or not to mark this document as | ||||
|          *        deleted (see CouchDB docs for _deleted) | ||||
|          */ | ||||
|         function CouchDocument(id, model, rev, markDeleted) { | ||||
|             return { | ||||
|                 "_id": id, | ||||
|                 "_rev": rev, | ||||
|                 "_deleted": markDeleted, | ||||
|                 "metadata": { | ||||
|                     "category": "domain object", | ||||
|                     "type": model.type, | ||||
|                     "owner": "admin", | ||||
|                     "name": model.name, | ||||
|                     "created": Date.now() | ||||
|                 }, | ||||
|                 "model": model | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return CouchDocument; | ||||
|     } | ||||
| ); | ||||
| @@ -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 () { | ||||
|  | ||||
|         // Set of connection states; changing among these states will be | ||||
|         // reflected in the indicator's appearance. | ||||
|         // CONNECTED: Everything nominal, expect to be able to read/write. | ||||
|         // DISCONNECTED: HTTP failed; maybe misconfigured, disconnected. | ||||
|         // SEMICONNECTED: Connected to the database, but it reported an error. | ||||
|         // PENDING: Still trying to connect, and haven't failed yet. | ||||
|         var CONNECTED = { | ||||
|                 text: "Connected", | ||||
|                 glyphClass: "ok", | ||||
|                 statusClass: "s-status-on", | ||||
|                 description: "Connected to the domain object database." | ||||
|             }, | ||||
|             DISCONNECTED = { | ||||
|                 text: "Disconnected", | ||||
|                 glyphClass: "err", | ||||
|                 statusClass: "s-status-caution", | ||||
|                 description: "Unable to connect to the domain object database." | ||||
|             }, | ||||
|             SEMICONNECTED = { | ||||
|                 text: "Unavailable", | ||||
|                 glyphClass: "caution", | ||||
|                 statusClass: "s-status-caution", | ||||
|                 description: "Database does not exist or is unavailable." | ||||
|             }, | ||||
|             PENDING = { | ||||
|                 text: "Checking connection...", | ||||
|                 statusClass: "s-status-caution" | ||||
|             }; | ||||
|  | ||||
|         /** | ||||
|          * Indicator for the current CouchDB connection. Polls CouchDB | ||||
|          * at a regular interval (defined by bundle constants) to ensure | ||||
|          * that the database is available. | ||||
|          * @constructor | ||||
|          * @memberof platform/persistence/couch | ||||
|          * @implements {Indicator} | ||||
|          * @param $http Angular's $http service | ||||
|          * @param $interval Angular's $interval service | ||||
|          * @param {string} path the URL to poll to check for couch availability | ||||
|          * @param {number} interval the interval, in milliseconds, to poll at | ||||
|          */ | ||||
|         function CouchIndicator($http, $interval, path, interval) { | ||||
|             var self = this; | ||||
|  | ||||
|             // Track the current connection state | ||||
|             this.state = PENDING; | ||||
|  | ||||
|             this.$http = $http; | ||||
|             this.$interval = $interval; | ||||
|             this.path = path; | ||||
|             this.interval = interval; | ||||
|  | ||||
|             // Callback if the HTTP request to Couch fails | ||||
|             function handleError() { | ||||
|                 self.state = DISCONNECTED; | ||||
|             } | ||||
|  | ||||
|             // Callback if the HTTP request succeeds. CouchDB may | ||||
|             // report an error, so check for that. | ||||
|             function handleResponse(response) { | ||||
|                 var data = response.data; | ||||
|                 self.state = data.error ? SEMICONNECTED : CONNECTED; | ||||
|             } | ||||
|  | ||||
|             // Try to connect to CouchDB, and update the indicator. | ||||
|             function updateIndicator() { | ||||
|                 $http.get(path).then(handleResponse, handleError); | ||||
|             } | ||||
|  | ||||
|             // Update the indicator initially, and start polling. | ||||
|             updateIndicator(); | ||||
|             $interval(updateIndicator, interval); | ||||
|         } | ||||
|  | ||||
|         CouchIndicator.prototype.getCssClass = function () { | ||||
|             return "c-indicator--clickable icon-suitcase " + this.state.statusClass; | ||||
|         }; | ||||
|  | ||||
|         CouchIndicator.prototype.getGlyphClass = function () { | ||||
|             return this.state.glyphClass; | ||||
|         }; | ||||
|  | ||||
|         CouchIndicator.prototype.getText = function () { | ||||
|             return this.state.text; | ||||
|         }; | ||||
|  | ||||
|         CouchIndicator.prototype.getDescription = function () { | ||||
|             return this.state.description; | ||||
|         }; | ||||
|  | ||||
|         return CouchIndicator; | ||||
|     } | ||||
| ); | ||||
| @@ -1,145 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * This bundle implements a persistence service which uses CouchDB to | ||||
|  * store documents. | ||||
|  * @namespace platform/persistence/cache | ||||
|  */ | ||||
| define( | ||||
|     ["./CouchDocument"], | ||||
|     function (CouchDocument) { | ||||
|  | ||||
|         // JSLint doesn't like dangling _'s, but CouchDB uses these, so | ||||
|         // hide this behind variables. | ||||
|         var REV = "_rev", | ||||
|             ID = "_id"; | ||||
|  | ||||
|         /** | ||||
|          * The CouchPersistenceProvider reads and writes JSON documents | ||||
|          * (more specifically, domain object models) to/from a CouchDB | ||||
|          * instance. | ||||
|          * @memberof platform/persistence/couch | ||||
|          * @constructor | ||||
|          * @implements {PersistenceService} | ||||
|          * @param $http Angular's $http service | ||||
|          * @param $interval Angular's $interval service | ||||
|          * @param {string} space the name of the persistence space being served | ||||
|          * @param {string} path the path to the CouchDB instance | ||||
|          */ | ||||
|         function CouchPersistenceProvider($http, $q, space, path) { | ||||
|             this.spaces = [space]; | ||||
|             this.revs = {}; | ||||
|             this.$q = $q; | ||||
|             this.$http = $http; | ||||
|             this.path = path; | ||||
|         } | ||||
|  | ||||
|         // Pull out a list of document IDs from CouchDB's | ||||
|         // _all_docs response | ||||
|         function getIdsFromAllDocs(allDocs) { | ||||
|             return allDocs.rows.map(function (r) { | ||||
|                 return r.id; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Check the response to a create/update/delete request; | ||||
|         // track the rev if it's valid, otherwise return false to | ||||
|         // indicate that the request failed. | ||||
|         CouchPersistenceProvider.prototype.checkResponse = function (response) { | ||||
|             if (response && response.ok) { | ||||
|                 this.revs[response.id] = response.rev; | ||||
|  | ||||
|                 return response.ok; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // Get a domain object model out of CouchDB's response | ||||
|         CouchPersistenceProvider.prototype.getModel = function (response) { | ||||
|             if (response && response.model) { | ||||
|                 this.revs[response[ID]] = response[REV]; | ||||
|  | ||||
|                 return response.model; | ||||
|             } else { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // Issue a request using $http; get back the plain JS object | ||||
|         // from the expected JSON response | ||||
|         CouchPersistenceProvider.prototype.request = function (subpath, method, value) { | ||||
|             return this.$http({ | ||||
|                 method: method, | ||||
|                 url: this.path + '/' + subpath, | ||||
|                 data: value | ||||
|             }).then(function (response) { | ||||
|                 return response.data; | ||||
|             }, function () { | ||||
|                 return undefined; | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Shorthand methods for GET/PUT methods | ||||
|         CouchPersistenceProvider.prototype.get = function (subpath) { | ||||
|             return this.request(subpath, "GET"); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.put = function (subpath, value) { | ||||
|             return this.request(subpath, "PUT", value); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.listSpaces = function () { | ||||
|             return this.$q.when(this.spaces); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.listObjects = function () { | ||||
|             return this.get("_all_docs").then(getIdsFromAllDocs.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.createObject = function (space, key, value) { | ||||
|             return this.put(key, new CouchDocument(key, value)) | ||||
|                 .then(this.checkResponse.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.readObject = function (space, key) { | ||||
|             return this.get(key).then(this.getModel.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.updateObject = function (space, key, value) { | ||||
|             var rev = this.revs[key]; | ||||
|  | ||||
|             return this.put(key, new CouchDocument(key, value, rev)) | ||||
|                 .then(this.checkResponse.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) { | ||||
|             var rev = this.revs[key]; | ||||
|  | ||||
|             return this.put(key, new CouchDocument(key, value, rev, true)) | ||||
|                 .then(this.checkResponse.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         return CouchPersistenceProvider; | ||||
|     } | ||||
| ); | ||||
| @@ -1,63 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * DomainObjectProviderSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../src/CouchDocument"], | ||||
|     function (CouchDocument) { | ||||
|  | ||||
|         // JSLint doesn't like dangling _'s, but CouchDB uses these, so | ||||
|         // hide this behind variables. | ||||
|         var REV = "_rev", | ||||
|             ID = "_id", | ||||
|             DELETED = "_deleted"; | ||||
|  | ||||
|         describe("A couch document", function () { | ||||
|             it("includes an id", function () { | ||||
|                 expect(new CouchDocument("testId", {})[ID]) | ||||
|                     .toEqual("testId"); | ||||
|             }); | ||||
|  | ||||
|             it("includes a rev only when one is provided", function () { | ||||
|                 expect(new CouchDocument("testId", {})[REV]) | ||||
|                     .not.toBeDefined(); | ||||
|                 expect(new CouchDocument("testId", {}, "testRev")[REV]) | ||||
|                     .toEqual("testRev"); | ||||
|             }); | ||||
|  | ||||
|             it("includes the provided model", function () { | ||||
|                 var model = { someKey: "some value" }; | ||||
|                 expect(new CouchDocument("testId", model).model) | ||||
|                     .toEqual(model); | ||||
|             }); | ||||
|  | ||||
|             it("marks documents as deleted only on request", function () { | ||||
|                 expect(new CouchDocument("testId", {}, "testRev")[DELETED]) | ||||
|                     .not.toBeDefined(); | ||||
|                 expect(new CouchDocument("testId", {}, "testRev", true)[DELETED]) | ||||
|                     .toBe(true); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,129 +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/CouchIndicator"], | ||||
|     function (CouchIndicator) { | ||||
|  | ||||
|         xdescribe("The CouchDB status indicator", function () { | ||||
|             var mockHttp, | ||||
|                 mockInterval, | ||||
|                 testPath, | ||||
|                 testInterval, | ||||
|                 mockPromise, | ||||
|                 indicator; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockHttp = jasmine.createSpyObj("$http", ["get"]); | ||||
|                 mockInterval = jasmine.createSpy("$interval"); | ||||
|                 mockPromise = jasmine.createSpyObj("promise", ["then"]); | ||||
|                 testPath = "/test/path"; | ||||
|                 testInterval = 12321; // Some number | ||||
|  | ||||
|                 mockHttp.get.and.returnValue(mockPromise); | ||||
|  | ||||
|                 indicator = new CouchIndicator( | ||||
|                     mockHttp, | ||||
|                     mockInterval, | ||||
|                     testPath, | ||||
|                     testInterval | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("polls for changes", function () { | ||||
|                 expect(mockInterval).toHaveBeenCalledWith( | ||||
|                     jasmine.any(Function), | ||||
|                     testInterval | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("has a database icon", function () { | ||||
|                 expect(indicator.getCssClass()).toEqual("icon-database s-status-caution"); | ||||
|             }); | ||||
|  | ||||
|             it("consults the database at the configured path", function () { | ||||
|                 expect(mockHttp.get).toHaveBeenCalledWith(testPath); | ||||
|             }); | ||||
|  | ||||
|             it("changes when the database connection is nominal", function () { | ||||
|                 var initialText = indicator.getText(), | ||||
|                     initialDescrption = indicator.getDescription(), | ||||
|                     initialGlyphClass = indicator.getGlyphClass(); | ||||
|  | ||||
|                 // Nominal just means getting back an object, without | ||||
|                 // an error field. | ||||
|                 mockPromise.then.calls.mostRecent().args[0]({ data: {} }); | ||||
|  | ||||
|                 // Verify that these values changed; | ||||
|                 // don't test for specific text. | ||||
|                 expect(indicator.getText()).not.toEqual(initialText); | ||||
|                 expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass); | ||||
|                 expect(indicator.getDescription()).not.toEqual(initialDescrption); | ||||
|  | ||||
|                 // Do check for specific class | ||||
|                 expect(indicator.getGlyphClass()).toEqual("ok"); | ||||
|             }); | ||||
|  | ||||
|             it("changes when the server reports an error", function () { | ||||
|                 var initialText = indicator.getText(), | ||||
|                     initialDescrption = indicator.getDescription(), | ||||
|                     initialGlyphClass = indicator.getGlyphClass(); | ||||
|  | ||||
|                 // Nominal just means getting back an object, with | ||||
|                 // an error field. | ||||
|                 mockPromise.then.calls.mostRecent().args[0]( | ||||
|                     { data: { error: "Uh oh." } } | ||||
|                 ); | ||||
|  | ||||
|                 // Verify that these values changed; | ||||
|                 // don't test for specific text. | ||||
|                 expect(indicator.getText()).not.toEqual(initialText); | ||||
|                 expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass); | ||||
|                 expect(indicator.getDescription()).not.toEqual(initialDescrption); | ||||
|  | ||||
|                 // Do check for specific class | ||||
|                 expect(indicator.getGlyphClass()).toEqual("caution"); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("changes when the server cannot be reached", function () { | ||||
|                 var initialText = indicator.getText(), | ||||
|                     initialDescrption = indicator.getDescription(), | ||||
|                     initialGlyphClass = indicator.getGlyphClass(); | ||||
|  | ||||
|                 // Nominal just means getting back an object, without | ||||
|                 // an error field. | ||||
|                 mockPromise.then.calls.mostRecent().args[1]({ data: {} }); | ||||
|  | ||||
|                 // Verify that these values changed; | ||||
|                 // don't test for specific text. | ||||
|                 expect(indicator.getText()).not.toEqual(initialText); | ||||
|                 expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass); | ||||
|                 expect(indicator.getDescription()).not.toEqual(initialDescrption); | ||||
|  | ||||
|                 // Do check for specific class | ||||
|                 expect(indicator.getGlyphClass()).toEqual("err"); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,223 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * DomainObjectProviderSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../src/CouchPersistenceProvider"], | ||||
|     function (CouchPersistenceProvider) { | ||||
|  | ||||
|         describe("The couch persistence provider", function () { | ||||
|             var mockHttp, | ||||
|                 mockQ, | ||||
|                 testSpace = "testSpace", | ||||
|                 testPath = "/test/db", | ||||
|                 capture, | ||||
|                 provider; | ||||
|  | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return mockPromise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockHttp = jasmine.createSpy("$http"); | ||||
|                 mockQ = jasmine.createSpyObj("$q", ["when"]); | ||||
|  | ||||
|                 mockQ.when.and.callFake(mockPromise); | ||||
|  | ||||
|                 // Capture promise results | ||||
|                 capture = jasmine.createSpy("capture"); | ||||
|  | ||||
|                 provider = new CouchPersistenceProvider( | ||||
|                     mockHttp, | ||||
|                     mockQ, | ||||
|                     testSpace, | ||||
|                     testPath | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("reports available spaces", function () { | ||||
|                 provider.listSpaces().then(capture); | ||||
|                 expect(capture).toHaveBeenCalledWith([testSpace]); | ||||
|             }); | ||||
|  | ||||
|             // General pattern of tests below is to simulate CouchDB's | ||||
|             // response, verify that request looks like what CouchDB | ||||
|             // would expect, and finally verify that CouchPersistenceProvider's | ||||
|             // return values match what is expected. | ||||
|             it("lists all available documents", function () { | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { rows: [{ id: "a" }, { id: "b" }, { id: "c" }] } | ||||
|                 })); | ||||
|                 provider.listObjects().then(capture); | ||||
|                 expect(mockHttp).toHaveBeenCalledWith({ | ||||
|                     url: "/test/db/_all_docs", // couch document listing | ||||
|                     method: "GET", | ||||
|                     data: undefined | ||||
|                 }); | ||||
|                 expect(capture).toHaveBeenCalledWith(["a", "b", "c"]); | ||||
|             }); | ||||
|  | ||||
|             it("allows object creation", function () { | ||||
|                 var model = { someKey: "some value" }; | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "ok": true | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.createObject("testSpace", "abc", model).then(capture); | ||||
|                 expect(mockHttp).toHaveBeenCalledWith({ | ||||
|                     url: "/test/db/abc", | ||||
|                     method: "PUT", | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": undefined, | ||||
|                         "_deleted": undefined, | ||||
|                         metadata: jasmine.any(Object), | ||||
|                         model: model | ||||
|                     } | ||||
|                 }); | ||||
|                 expect(capture).toHaveBeenCalledWith(true); | ||||
|             }); | ||||
|  | ||||
|             it("allows object models to be read back", function () { | ||||
|                 var model = { someKey: "some value" }; | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "model": model | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.readObject("testSpace", "abc").then(capture); | ||||
|                 expect(mockHttp).toHaveBeenCalledWith({ | ||||
|                     url: "/test/db/abc", | ||||
|                     method: "GET", | ||||
|                     data: undefined | ||||
|                 }); | ||||
|                 expect(capture).toHaveBeenCalledWith(model); | ||||
|             }); | ||||
|  | ||||
|             it("allows object update", function () { | ||||
|                 var model = { someKey: "some value" }; | ||||
|  | ||||
|                 // First do a read to populate rev tags... | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "model": {} | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.readObject("testSpace", "abc"); | ||||
|  | ||||
|                 // Now perform an update | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "uvw", | ||||
|                         "ok": true | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.updateObject("testSpace", "abc", model).then(capture); | ||||
|                 expect(mockHttp).toHaveBeenCalledWith({ | ||||
|                     url: "/test/db/abc", | ||||
|                     method: "PUT", | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "_deleted": undefined, | ||||
|                         metadata: jasmine.any(Object), | ||||
|                         model: model | ||||
|                     } | ||||
|                 }); | ||||
|                 expect(capture).toHaveBeenCalledWith(true); | ||||
|             }); | ||||
|  | ||||
|             it("allows object deletion", function () { | ||||
|                 // First do a read to populate rev tags... | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "model": {} | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.readObject("testSpace", "abc"); | ||||
|  | ||||
|                 // Now perform an update | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "uvw", | ||||
|                         "ok": true | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.deleteObject("testSpace", "abc", {}).then(capture); | ||||
|                 expect(mockHttp).toHaveBeenCalledWith({ | ||||
|                     url: "/test/db/abc", | ||||
|                     method: "PUT", | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "_deleted": true, | ||||
|                         metadata: jasmine.any(Object), | ||||
|                         model: {} | ||||
|                     } | ||||
|                 }); | ||||
|                 expect(capture).toHaveBeenCalledWith(true); | ||||
|             }); | ||||
|  | ||||
|             it("reports failure to create objects", function () { | ||||
|                 var model = { someKey: "some value" }; | ||||
|                 mockHttp.and.returnValue(mockPromise({ | ||||
|                     data: { | ||||
|                         "_id": "abc", | ||||
|                         "_rev": "xyz", | ||||
|                         "ok": false | ||||
|                     } | ||||
|                 })); | ||||
|                 provider.createObject("testSpace", "abc", model).then(capture); | ||||
|                 expect(capture).toHaveBeenCalledWith(false); | ||||
|             }); | ||||
|  | ||||
|             it("returns undefined when objects are not found", function () { | ||||
|                 // Act like a 404 | ||||
|                 mockHttp.and.returnValue({ | ||||
|                     then: function (success, fail) { | ||||
|                         return mockPromise(fail()); | ||||
|                     } | ||||
|                 }); | ||||
|                 provider.readObject("testSpace", "abc").then(capture); | ||||
|                 expect(capture).toHaveBeenCalledWith(undefined); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -52,7 +52,7 @@ define([ | ||||
|  | ||||
|             oldStyleObject.getCapability('mutation').mutate(function () { | ||||
|                 return utils.toOldFormat(newStyleObject); | ||||
|             }); | ||||
|             }, newStyleObject.modified); | ||||
|  | ||||
|             removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation); | ||||
|         }.bind(this); | ||||
|   | ||||
| @@ -76,7 +76,10 @@ class MutableDomainObject { | ||||
|     } | ||||
|     $set(path, value) { | ||||
|         _.set(this, path, value); | ||||
|         _.set(this, 'modified', Date.now()); | ||||
|  | ||||
|         if (path !== 'persisted' && path !== 'modified') { | ||||
|             _.set(this, 'modified', Date.now()); | ||||
|         } | ||||
|  | ||||
|         //Emit secret synchronization event first, so that all objects are in sync before subsequent events fired. | ||||
|         this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this); | ||||
|   | ||||
| @@ -520,8 +520,10 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) { | ||||
|  */ | ||||
|  | ||||
| function hasAlreadyBeenPersisted(domainObject) { | ||||
|     return domainObject.persisted !== undefined | ||||
|         && domainObject.persisted === domainObject.modified; | ||||
|     const result = domainObject.persisted !== undefined | ||||
|         && domainObject.persisted >= domainObject.modified; | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| export default ObjectAPI; | ||||
|   | ||||
| @@ -90,7 +90,6 @@ define([ | ||||
|     '../platform/framework/src/load/Bundle', | ||||
|     '../platform/identity/bundle', | ||||
|     '../platform/persistence/aggregator/bundle', | ||||
|     '../platform/persistence/couch/bundle', | ||||
|     '../platform/persistence/elastic/bundle', | ||||
|     '../platform/persistence/local/bundle', | ||||
|     '../platform/persistence/queue/bundle', | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import CouchObjectQueue from "./CouchObjectQueue"; | ||||
| const REV = "_rev"; | ||||
| const ID = "_id"; | ||||
| const HEARTBEAT = 50000; | ||||
| const ALL_DOCS = "_all_docs?include_docs=true"; | ||||
|  | ||||
| export default class CouchObjectProvider { | ||||
|     // options { | ||||
| @@ -41,6 +42,8 @@ export default class CouchObjectProvider { | ||||
|         this.objectQueue = {}; | ||||
|         this.observeEnabled = options.disableObserve !== true; | ||||
|         this.observers = {}; | ||||
|         this.batchIds = []; | ||||
|  | ||||
|         if (this.observeEnabled) { | ||||
|             this.observeObjectChanges(options.filter); | ||||
|         } | ||||
| @@ -67,6 +70,9 @@ export default class CouchObjectProvider { | ||||
|         // stringify body if needed | ||||
|         if (fetchOptions.body) { | ||||
|             fetchOptions.body = JSON.stringify(fetchOptions.body); | ||||
|             fetchOptions.headers = { | ||||
|                 "Content-Type": "application/json" | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return fetch(this.url + '/' + subPath, fetchOptions) | ||||
| @@ -78,14 +84,18 @@ export default class CouchObjectProvider { | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     // Check the response to a create/update/delete request; | ||||
|     // track the rev if it's valid, otherwise return false to | ||||
|     // indicate that the request failed. | ||||
|     // persist any queued objects | ||||
|     /** | ||||
|      * Check the response to a create/update/delete request; | ||||
|      * track the rev if it's valid, otherwise return false to | ||||
|      * indicate that the request failed. | ||||
|      * persist any queued objects | ||||
|      * @private | ||||
|      */ | ||||
|     checkResponse(response, intermediateResponse) { | ||||
|         let requestSuccess = false; | ||||
|         const id = response ? response.id : undefined; | ||||
|         let rev; | ||||
|  | ||||
|         if (response && response.ok) { | ||||
|             rev = response.rev; | ||||
|             requestSuccess = true; | ||||
| @@ -106,6 +116,9 @@ export default class CouchObjectProvider { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     getModel(response) { | ||||
|         if (response && response.model) { | ||||
|             let key = response[ID]; | ||||
| @@ -131,10 +144,118 @@ export default class CouchObjectProvider { | ||||
|     } | ||||
|  | ||||
|     get(identifier, abortSignal) { | ||||
|         return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this)); | ||||
|         this.batchIds.push(identifier.key); | ||||
|  | ||||
|         if (this.bulkPromise === undefined) { | ||||
|             this.bulkPromise = this.deferBatchedGet(abortSignal); | ||||
|         } | ||||
|  | ||||
|         return this.bulkPromise | ||||
|             .then((domainObjectMap) => { | ||||
|                 return domainObjectMap[identifier.key]; | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     async getObjectsByFilter(filter) { | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     deferBatchedGet(abortSignal) { | ||||
|         // We until the next event loop cycle to "collect" all of the get | ||||
|         // requests triggered in this iteration of the event loop | ||||
|  | ||||
|         return this.waitOneEventCycle().then(() => { | ||||
|             let batchIds = this.batchIds; | ||||
|  | ||||
|             this.clearBatch(); | ||||
|  | ||||
|             if (batchIds.length === 1) { | ||||
|                 let objectKey = batchIds[0]; | ||||
|  | ||||
|                 //If there's only one request, just do a regular get | ||||
|                 return this.request(objectKey, "GET", undefined, abortSignal) | ||||
|                     .then(this.returnAsMap(objectKey)); | ||||
|             } else { | ||||
|                 return this.bulkGet(batchIds, abortSignal); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     returnAsMap(objectKey) { | ||||
|         return (result) => { | ||||
|             let objectMap = {}; | ||||
|             objectMap[objectKey] = this.getModel(result); | ||||
|  | ||||
|             return objectMap; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     clearBatch() { | ||||
|         this.batchIds = []; | ||||
|         delete this.bulkPromise; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     waitOneEventCycle() { | ||||
|         return new Promise((resolve) => { | ||||
|             setTimeout(resolve); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     bulkGet(ids, signal) { | ||||
|         ids = this.removeDuplicates(ids); | ||||
|  | ||||
|         const query = { | ||||
|             'keys': ids | ||||
|         }; | ||||
|  | ||||
|         return this.request(ALL_DOCS, 'POST', query, signal).then((response) => { | ||||
|             if (response && response.rows !== undefined) { | ||||
|                 return response.rows.reduce((map, row) => { | ||||
|                     if (row.doc !== undefined) { | ||||
|                         map[row.key] = this.getModel(row.doc); | ||||
|                     } | ||||
|  | ||||
|                     return map; | ||||
|                 }, {}); | ||||
|             } else { | ||||
|                 return {}; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     removeDuplicates(array) { | ||||
|         return Array.from(new Set(array)); | ||||
|     } | ||||
|  | ||||
|     search(query, abortSignal) { | ||||
|         const filter = { | ||||
|             "selector": { | ||||
|                 "model": { | ||||
|                     "name": { | ||||
|                         "$regex": `(?i)${query}` | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return this.getObjectsByFilter(filter, abortSignal); | ||||
|     } | ||||
|  | ||||
|     async getObjectsByFilter(filter, abortSignal) { | ||||
|         let objects = []; | ||||
|  | ||||
|         let url = `${this.url}/_find`; | ||||
| @@ -149,6 +270,7 @@ export default class CouchObjectProvider { | ||||
|             headers: { | ||||
|                 "Content-Type": "application/json" | ||||
|             }, | ||||
|             signal: abortSignal, | ||||
|             body | ||||
|         }); | ||||
|  | ||||
| @@ -203,6 +325,9 @@ export default class CouchObjectProvider { | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     abortGetChanges() { | ||||
|         if (this.controller) { | ||||
|             this.controller.abort(); | ||||
| @@ -212,6 +337,9 @@ export default class CouchObjectProvider { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     async observeObjectChanges(filter) { | ||||
|         let intermediateResponse = this.getIntermediateResponse(); | ||||
|  | ||||
| @@ -292,6 +420,9 @@ export default class CouchObjectProvider { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     getIntermediateResponse() { | ||||
|         let intermediateResponse = {}; | ||||
|         intermediateResponse.promise = new Promise(function (resolve, reject) { | ||||
| @@ -302,6 +433,9 @@ export default class CouchObjectProvider { | ||||
|         return intermediateResponse; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     enqueueObject(key, model, intermediateResponse) { | ||||
|         if (this.objectQueue[key]) { | ||||
|             this.objectQueue[key].enqueue({ | ||||
| @@ -330,6 +464,9 @@ export default class CouchObjectProvider { | ||||
|         return intermediateResponse.promise; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     updateQueued(key) { | ||||
|         if (!this.objectQueue[key].pending) { | ||||
|             this.objectQueue[key].pending = true; | ||||
|   | ||||
| @@ -24,7 +24,6 @@ import { | ||||
|     createOpenMct, | ||||
|     resetApplicationState, spyOnBuiltins | ||||
| } from 'utils/testing'; | ||||
| import CouchObjectProvider from './CouchObjectProvider'; | ||||
|  | ||||
| describe('the plugin', () => { | ||||
|     let openmct; | ||||
| @@ -42,7 +41,8 @@ describe('the plugin', () => { | ||||
|                 namespace: '', | ||||
|                 key: 'some-value' | ||||
|             }, | ||||
|             type: 'mock-type' | ||||
|             type: 'mock-type', | ||||
|             modified: 0 | ||||
|         }; | ||||
|         options = { | ||||
|             url: testPath, | ||||
| @@ -95,6 +95,7 @@ describe('the plugin', () => { | ||||
|                     return { | ||||
|                         ok: true, | ||||
|                         _id: 'some-value', | ||||
|                         id: 'some-value', | ||||
|                         _rev: 1, | ||||
|                         model: {} | ||||
|                     }; | ||||
| @@ -104,44 +105,130 @@ describe('the plugin', () => { | ||||
|         }); | ||||
|  | ||||
|         it('gets an object', () => { | ||||
|             openmct.objects.get(mockDomainObject.identifier).then((result) => { | ||||
|             return openmct.objects.get(mockDomainObject.identifier).then((result) => { | ||||
|                 expect(result.identifier.key).toEqual(mockDomainObject.identifier.key); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('creates an object', () => { | ||||
|             openmct.objects.save(mockDomainObject).then((result) => { | ||||
|             return openmct.objects.save(mockDomainObject).then((result) => { | ||||
|                 expect(provider.create).toHaveBeenCalled(); | ||||
|                 expect(result).toBeTrue(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('updates an object', () => { | ||||
|             openmct.objects.save(mockDomainObject).then((result) => { | ||||
|             return openmct.objects.save(mockDomainObject).then((result) => { | ||||
|                 expect(result).toBeTrue(); | ||||
|                 expect(provider.create).toHaveBeenCalled(); | ||||
|                 openmct.objects.save(mockDomainObject).then((updatedResult) => { | ||||
|  | ||||
|                 //Set modified timestamp it detects a change and persists the updated model. | ||||
|                 mockDomainObject.modified = Date.now(); | ||||
|  | ||||
|                 return openmct.objects.save(mockDomainObject).then((updatedResult) => { | ||||
|                     expect(updatedResult).toBeTrue(); | ||||
|                     expect(provider.update).toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     describe('batches requests', () => { | ||||
|         let mockPromise; | ||||
|         beforeEach(() => { | ||||
|             mockPromise = Promise.resolve({ | ||||
|                 json: () => { | ||||
|                     return { | ||||
|                         total_rows: 0, | ||||
|                         rows: [] | ||||
|                     }; | ||||
|                 } | ||||
|             }); | ||||
|             fetch.and.returnValue(mockPromise); | ||||
|         }); | ||||
|         it('for multiple simultaneous gets', () => { | ||||
|             const objectIds = [ | ||||
|                 { | ||||
|                     namespace: '', | ||||
|                     key: 'object-1' | ||||
|                 }, { | ||||
|                     namespace: '', | ||||
|                     key: 'object-2' | ||||
|                 }, { | ||||
|                     namespace: '', | ||||
|                     key: 'object-3' | ||||
|                 } | ||||
|             ]; | ||||
|  | ||||
|         it('updates queued objects', () => { | ||||
|             let couchProvider = new CouchObjectProvider(openmct, options, ''); | ||||
|             let intermediateResponse = couchProvider.getIntermediateResponse(); | ||||
|             spyOn(couchProvider, 'updateQueued'); | ||||
|             couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse); | ||||
|             couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1); | ||||
|             couchProvider.update(mockDomainObject); | ||||
|             expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2); | ||||
|             couchProvider.checkResponse({ | ||||
|                 ok: true, | ||||
|                 rev: 2, | ||||
|                 id: mockDomainObject.identifier.key | ||||
|             }, intermediateResponse); | ||||
|             const getAllObjects = Promise.all( | ||||
|                 objectIds.map((identifier) => | ||||
|                     openmct.objects.get(identifier) | ||||
|                 )); | ||||
|  | ||||
|             expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2); | ||||
|             return getAllObjects.then(() => { | ||||
|                 const requestUrl = fetch.calls.mostRecent().args[0]; | ||||
|                 const requestMethod = fetch.calls.mostRecent().args[1].method; | ||||
|  | ||||
|                 expect(fetch).toHaveBeenCalledTimes(1); | ||||
|                 expect(requestUrl.includes('_all_docs')).toBeTrue(); | ||||
|                 expect(requestMethod).toEqual('POST'); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('but not for single gets', () => { | ||||
|             const objectId = { | ||||
|                 namespace: '', | ||||
|                 key: 'object-1' | ||||
|             }; | ||||
|  | ||||
|             const getObject = openmct.objects.get(objectId); | ||||
|  | ||||
|             return getObject.then(() => { | ||||
|                 const requestUrl = fetch.calls.mostRecent().args[0]; | ||||
|                 const requestMethod = fetch.calls.mostRecent().args[1].method; | ||||
|  | ||||
|                 expect(fetch).toHaveBeenCalledTimes(1); | ||||
|                 expect(requestUrl.endsWith(`${objectId.key}`)).toBeTrue(); | ||||
|                 expect(requestMethod).toEqual('GET'); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     describe('implements server-side search', () => { | ||||
|         let mockPromise; | ||||
|         beforeEach(() => { | ||||
|             mockPromise = Promise.resolve({ | ||||
|                 body: { | ||||
|                     getReader() { | ||||
|                         return { | ||||
|                             read() { | ||||
|                                 return Promise.resolve({ | ||||
|                                     done: true, | ||||
|                                     value: undefined | ||||
|                                 }); | ||||
|                             } | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|             fetch.and.returnValue(mockPromise); | ||||
|         }); | ||||
|  | ||||
|         it("using Couch's 'find' endpoint", () => { | ||||
|             return Promise.all(openmct.objects.search('test')).then(() => { | ||||
|                 const requestUrl = fetch.calls.mostRecent().args[0]; | ||||
|  | ||||
|                 expect(fetch).toHaveBeenCalled(); | ||||
|                 expect(requestUrl.endsWith('_find')).toBeTrue(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("and supports search by object name", () => { | ||||
|             return Promise.all(openmct.objects.search('test')).then(() => { | ||||
|                 const requestPayload = JSON.parse(fetch.calls.mostRecent().args[1].body); | ||||
|  | ||||
|                 expect(requestPayload).toBeDefined(); | ||||
|                 expect(requestPayload.selector.model.name.$regex).toEqual('(?i)test'); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -710,25 +710,25 @@ export default { | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         async aggregateSearchResults(results, abortSignal) { | ||||
|         aggregateSearchResults(results, abortSignal) { | ||||
|             for (const result of results) { | ||||
|                 if (!abortSignal.aborted) { | ||||
|                     const objectPath = await this.openmct.objects.getOriginalPath(result.identifier); | ||||
|                     this.openmct.objects.getOriginalPath(result.identifier).then((objectPath) => { | ||||
|                         // removing the item itself, as the path we pass to buildTreeItem is a parent path | ||||
|                         objectPath.shift(); | ||||
|  | ||||
|                     // removing the item itself, as the path we pass to buildTreeItem is a parent path | ||||
|                     objectPath.shift(); | ||||
|                         // if root, remove, we're not using in object path for tree | ||||
|                         let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false; | ||||
|                         if (lastObject && lastObject.type === 'root') { | ||||
|                             objectPath.pop(); | ||||
|                         } | ||||
|  | ||||
|                     // if root, remove, we're not using in object path for tree | ||||
|                     let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false; | ||||
|                     if (lastObject && lastObject.type === 'root') { | ||||
|                         objectPath.pop(); | ||||
|                     } | ||||
|                         // we reverse the objectPath in the tree, so have to do it here first, | ||||
|                         // since this one is already in the correct direction | ||||
|                         let resultObject = this.buildTreeItem(result, objectPath.reverse()); | ||||
|  | ||||
|                     // we reverse the objectPath in the tree, so have to do it here first, | ||||
|                     // since this one is already in the correct direction | ||||
|                     let resultObject = this.buildTreeItem(result, objectPath.reverse()); | ||||
|  | ||||
|                     this.searchResultItems.push(resultObject); | ||||
|                         this.searchResultItems.push(resultObject); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user