Compare commits
	
		
			15 Commits
		
	
	
		
			v3.2.0
			...
			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