mirror of
https://github.com/nasa/openmct.git
synced 2025-06-06 09:21:43 +00:00
Couchdb object synchronization (#3674)
* Implements ObjectAPI changes to refresh objects when an update is received from the database. * Populates a virtual folder of plans from CouchDB * Fixes bug with supportsMutation API call parameters
This commit is contained in:
parent
dd3d4c8c3a
commit
29128a891d
@ -219,7 +219,7 @@ define([
|
|||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name objects
|
* @name objects
|
||||||
*/
|
*/
|
||||||
this.objects = new api.ObjectAPI.default(this.types);
|
this.objects = new api.ObjectAPI.default(this.types, this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for retrieving and interpreting telemetry data associated
|
* An interface for retrieving and interpreting telemetry data associated
|
||||||
|
@ -196,7 +196,7 @@ define([
|
|||||||
|
|
||||||
this.provider.add(this.domainObject, child.identifier);
|
this.provider.add(this.domainObject, child.identifier);
|
||||||
} else {
|
} else {
|
||||||
if (this.returnMutables && this.publicAPI.objects.supportsMutation(child)) {
|
if (this.returnMutables && this.publicAPI.objects.supportsMutation(child.identifier)) {
|
||||||
let keyString = this.publicAPI.objects.makeKeyString(child.identifier);
|
let keyString = this.publicAPI.objects.makeKeyString(child.identifier);
|
||||||
|
|
||||||
child = this.publicAPI.objects._toMutable(child);
|
child = this.publicAPI.objects._toMutable(child);
|
||||||
|
@ -96,6 +96,16 @@ class MutableDomainObject {
|
|||||||
//TODO: Emit events for listeners of child properties when parent changes.
|
//TODO: Emit events for listeners of child properties when parent changes.
|
||||||
// Do it at observer time - also register observers for parent attribute path.
|
// Do it at observer time - also register observers for parent attribute path.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$refresh(model) {
|
||||||
|
//TODO: Currently we are updating the entire object.
|
||||||
|
// In the future we could update a specific property of the object using the 'path' parameter.
|
||||||
|
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), model);
|
||||||
|
|
||||||
|
//Emit wildcard event, with path so that callback knows what changed
|
||||||
|
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this);
|
||||||
|
}
|
||||||
|
|
||||||
$on(event, callback) {
|
$on(event, callback) {
|
||||||
this._instanceEventEmitter.on(event, callback);
|
this._instanceEventEmitter.on(event, callback);
|
||||||
|
|
||||||
|
@ -33,11 +33,15 @@ import InterceptorRegistry from './InterceptorRegistry';
|
|||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ObjectAPI(typeRegistry) {
|
function ObjectAPI(typeRegistry, openmct) {
|
||||||
this.typeRegistry = typeRegistry;
|
this.typeRegistry = typeRegistry;
|
||||||
this.eventEmitter = new EventEmitter();
|
this.eventEmitter = new EventEmitter();
|
||||||
this.providers = {};
|
this.providers = {};
|
||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
|
this.injectIdentifierService = function () {
|
||||||
|
this.identifierService = openmct.$injector.get("identifierService");
|
||||||
|
};
|
||||||
|
|
||||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.interceptorRegistry = new InterceptorRegistry();
|
this.interceptorRegistry = new InterceptorRegistry();
|
||||||
@ -51,16 +55,33 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
|||||||
this.fallbackProvider = p;
|
this.fallbackProvider = p;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.getIdentifierService = function () {
|
||||||
|
// Lazily acquire identifier service
|
||||||
|
if (!this.identifierService) {
|
||||||
|
this.injectIdentifierService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.identifierService;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the provider for a given identifier.
|
* Retrieve the provider for a given identifier.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.getProvider = function (identifier) {
|
ObjectAPI.prototype.getProvider = function (identifier) {
|
||||||
|
//handles the '' vs 'mct' namespace issue
|
||||||
|
const keyString = utils.makeKeyString(identifier);
|
||||||
|
const identifierService = this.getIdentifierService();
|
||||||
|
const namespace = identifierService.parse(keyString).getSpace();
|
||||||
|
|
||||||
if (identifier.key === 'ROOT') {
|
if (identifier.key === 'ROOT') {
|
||||||
return this.rootProvider;
|
return this.rootProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.providers[identifier.namespace] || this.fallbackProvider;
|
return this.providers[namespace] || this.fallbackProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,13 +228,29 @@ ObjectAPI.prototype.search = function (query, options) {
|
|||||||
* @returns {Promise.<MutableDomainObject>} a promise that will resolve with a MutableDomainObject if
|
* @returns {Promise.<MutableDomainObject>} a promise that will resolve with a MutableDomainObject if
|
||||||
* the object can be mutated.
|
* the object can be mutated.
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.getMutable = function (identifier) {
|
ObjectAPI.prototype.getMutable = function (idOrKeyString) {
|
||||||
if (!this.supportsMutation(identifier)) {
|
if (!this.supportsMutation(idOrKeyString)) {
|
||||||
throw new Error(`Object "${this.makeKeyString(identifier)}" does not support mutation.`);
|
throw new Error(`Object "${this.makeKeyString(idOrKeyString)}" does not support mutation.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get(identifier).then((object) => {
|
return this.get(idOrKeyString).then((object) => {
|
||||||
return this._toMutable(object);
|
const mutableDomainObject = this._toMutable(object);
|
||||||
|
|
||||||
|
// Check if provider supports realtime updates
|
||||||
|
let identifier = utils.parseKeyString(idOrKeyString);
|
||||||
|
let provider = this.getProvider(identifier);
|
||||||
|
|
||||||
|
if (provider !== undefined
|
||||||
|
&& provider.observe !== undefined) {
|
||||||
|
let unobserve = provider.observe(identifier, (updatedModel) => {
|
||||||
|
mutableDomainObject.$refresh(updatedModel);
|
||||||
|
});
|
||||||
|
mutableDomainObject.$on('$destroy', () => {
|
||||||
|
unobserve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutableDomainObject;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ import ObjectAPI from './ObjectAPI.js';
|
|||||||
describe("The Object API", () => {
|
describe("The Object API", () => {
|
||||||
let objectAPI;
|
let objectAPI;
|
||||||
let typeRegistry;
|
let typeRegistry;
|
||||||
|
let openmct = {};
|
||||||
|
let mockIdentifierService;
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
const TEST_NAMESPACE = "test-namespace";
|
const TEST_NAMESPACE = "test-namespace";
|
||||||
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||||
@ -11,7 +13,19 @@ describe("The Object API", () => {
|
|||||||
typeRegistry = jasmine.createSpyObj('typeRegistry', [
|
typeRegistry = jasmine.createSpyObj('typeRegistry', [
|
||||||
'get'
|
'get'
|
||||||
]);
|
]);
|
||||||
objectAPI = new ObjectAPI(typeRegistry);
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
|
'identifierService',
|
||||||
|
['parse']
|
||||||
|
);
|
||||||
|
mockIdentifierService.parse.and.returnValue({
|
||||||
|
getSpace: () => {
|
||||||
|
return TEST_NAMESPACE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||||
|
objectAPI = new ObjectAPI(typeRegistry, openmct);
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
@ -136,11 +150,13 @@ describe("The Object API", () => {
|
|||||||
|
|
||||||
describe("the mutation API", () => {
|
describe("the mutation API", () => {
|
||||||
let testObject;
|
let testObject;
|
||||||
|
let updatedTestObject;
|
||||||
let mutable;
|
let mutable;
|
||||||
let mockProvider;
|
let mockProvider;
|
||||||
|
let callbacks = [];
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
objectAPI = new ObjectAPI(typeRegistry);
|
objectAPI = new ObjectAPI(typeRegistry, openmct);
|
||||||
testObject = {
|
testObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
@ -154,12 +170,27 @@ describe("The Object API", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||||
"get",
|
"get",
|
||||||
"create",
|
"create",
|
||||||
"update"
|
"update",
|
||||||
|
"observe",
|
||||||
|
"observeObjectChanges"
|
||||||
]);
|
]);
|
||||||
mockProvider.get.and.returnValue(Promise.resolve(testObject));
|
mockProvider.get.and.returnValue(Promise.resolve(testObject));
|
||||||
|
mockProvider.observeObjectChanges.and.callFake(() => {
|
||||||
|
callbacks[0](updatedTestObject);
|
||||||
|
callbacks.splice(0, 1);
|
||||||
|
});
|
||||||
|
mockProvider.observe.and.callFake((id, callback) => {
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
callbacks.push(callback);
|
||||||
|
} else {
|
||||||
|
callbacks[0] = callback;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
|
|
||||||
return objectAPI.getMutable(testObject.identifier)
|
return objectAPI.getMutable(testObject.identifier)
|
||||||
@ -191,6 +222,13 @@ describe("The Object API", () => {
|
|||||||
it('that is identical to original object when serialized', function () {
|
it('that is identical to original object when serialized', function () {
|
||||||
expect(JSON.stringify(mutable)).toEqual(JSON.stringify(testObject));
|
expect(JSON.stringify(mutable)).toEqual(JSON.stringify(testObject));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('that observes for object changes', function () {
|
||||||
|
let mockListener = jasmine.createSpy('mockListener');
|
||||||
|
objectAPI.observe(testObject, '*', mockListener);
|
||||||
|
mockProvider.observeObjectChanges();
|
||||||
|
expect(mockListener).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('uses events', function () {
|
describe('uses events', function () {
|
||||||
|
37
src/plugins/CouchDBSearchFolder/plugin.js
Normal file
37
src/plugins/CouchDBSearchFolder/plugin.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export default function (couchPlugin, searchFilter) {
|
||||||
|
return function install(openmct) {
|
||||||
|
const couchProvider = couchPlugin.couchProvider;
|
||||||
|
|
||||||
|
openmct.objects.addRoot({
|
||||||
|
namespace: 'couch-search',
|
||||||
|
key: 'couch-search'
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.objects.addProvider('couch-search', {
|
||||||
|
get(identifier) {
|
||||||
|
if (identifier.key !== 'couch-search') {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({
|
||||||
|
identifier,
|
||||||
|
type: 'folder',
|
||||||
|
name: "CouchDB Documents"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.composition.addProvider({
|
||||||
|
appliesTo(domainObject) {
|
||||||
|
return domainObject.identifier.namespace === 'couch-search'
|
||||||
|
&& domainObject.identifier.key === 'couch-search';
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
return couchProvider.getObjectsByFilter(searchFilter).then(objects => {
|
||||||
|
return objects.map(object => object.identifier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
91
src/plugins/CouchDBSearchFolder/pluginSpec.js
Normal file
91
src/plugins/CouchDBSearchFolder/pluginSpec.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import {createOpenMct, resetApplicationState} from "utils/testing";
|
||||||
|
import CouchDBSearchFolderPlugin from './plugin';
|
||||||
|
|
||||||
|
describe('the plugin', function () {
|
||||||
|
let identifier = {
|
||||||
|
namespace: 'couch-search',
|
||||||
|
key: "couch-search"
|
||||||
|
};
|
||||||
|
let testPath = '/test/db';
|
||||||
|
let openmct;
|
||||||
|
let composition;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
let couchPlugin = openmct.plugins.CouchDB(testPath);
|
||||||
|
openmct.install(couchPlugin);
|
||||||
|
|
||||||
|
openmct.install(new CouchDBSearchFolderPlugin(couchPlugin, {
|
||||||
|
"selector": {
|
||||||
|
"model": {
|
||||||
|
"type": "plan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
composition = openmct.composition.get({identifier});
|
||||||
|
|
||||||
|
spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([
|
||||||
|
{
|
||||||
|
identifier: {
|
||||||
|
key: "1",
|
||||||
|
namespace: "mct"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: {
|
||||||
|
key: "2",
|
||||||
|
namespace: "mct"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides a folder to hold plans', () => {
|
||||||
|
openmct.objects.get(identifier).then((object) => {
|
||||||
|
expect(object).toEqual({
|
||||||
|
identifier,
|
||||||
|
type: 'folder',
|
||||||
|
name: "CouchDB Documents"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides composition for couch search folders', () => {
|
||||||
|
composition.load().then((objects) => {
|
||||||
|
expect(objects.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -109,10 +109,23 @@ const selectedPage = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
|
let mockIdentifierService;
|
||||||
|
|
||||||
describe('Notebook Entries:', () => {
|
describe('Notebook Entries:', () => {
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
|
'identifierService',
|
||||||
|
['parse']
|
||||||
|
);
|
||||||
|
mockIdentifierService.parse.and.returnValue({
|
||||||
|
getSpace: () => {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||||
openmct.types.addType('notebook', {
|
openmct.types.addType('notebook', {
|
||||||
creatable: true
|
creatable: true
|
||||||
});
|
});
|
||||||
|
@ -25,13 +25,36 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
|||||||
|
|
||||||
const REV = "_rev";
|
const REV = "_rev";
|
||||||
const ID = "_id";
|
const ID = "_id";
|
||||||
|
const HEARTBEAT = 50000;
|
||||||
|
|
||||||
export default class CouchObjectProvider {
|
export default class CouchObjectProvider {
|
||||||
constructor(openmct, url, namespace) {
|
// options {
|
||||||
|
// url: couchdb url,
|
||||||
|
// disableObserve: disable auto feed from couchdb to keep objects in sync,
|
||||||
|
// filter: selector to find objects to sync in couchdb
|
||||||
|
// }
|
||||||
|
constructor(openmct, options, namespace) {
|
||||||
|
options = this._normalize(options);
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.url = url;
|
this.url = options.url;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
this.objectQueue = {};
|
this.objectQueue = {};
|
||||||
|
this.observeEnabled = options.disableObserve !== true;
|
||||||
|
this.observers = {};
|
||||||
|
if (this.observeEnabled) {
|
||||||
|
this.observeObjectChanges(options.filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//backwards compatibility, options used to be a url. Now it's an object
|
||||||
|
_normalize(options) {
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
return {
|
||||||
|
url: options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
request(subPath, method, value) {
|
request(subPath, method, value) {
|
||||||
@ -102,6 +125,162 @@ export default class CouchObjectProvider {
|
|||||||
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getObjectsByFilter(filter) {
|
||||||
|
let objects = [];
|
||||||
|
|
||||||
|
let url = `${this.url}/_find`;
|
||||||
|
let body = {};
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
body = JSON.stringify(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
let completed = false;
|
||||||
|
|
||||||
|
while (!completed) {
|
||||||
|
const {done, value} = await reader.read();
|
||||||
|
//done is true when we lose connection with the provider
|
||||||
|
if (done) {
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
let chunk = new Uint8Array(value.length);
|
||||||
|
chunk.set(value, 0);
|
||||||
|
const decodedChunk = new TextDecoder("utf-8").decode(chunk);
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(decodedChunk);
|
||||||
|
if (json) {
|
||||||
|
let docs = json.docs;
|
||||||
|
docs.forEach(doc => {
|
||||||
|
let object = this.getModel(doc);
|
||||||
|
if (object) {
|
||||||
|
objects.push(object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(identifier, callback) {
|
||||||
|
if (!this.observeEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
this.observers[keyString] = this.observers[keyString] || [];
|
||||||
|
this.observers[keyString].push(callback);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
abortGetChanges() {
|
||||||
|
if (this.controller) {
|
||||||
|
this.controller.abort();
|
||||||
|
this.controller = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async observeObjectChanges(filter) {
|
||||||
|
let intermediateResponse = this.getIntermediateResponse();
|
||||||
|
|
||||||
|
if (!this.observeEnabled) {
|
||||||
|
intermediateResponse.reject('Observe for changes is disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const signal = controller.signal;
|
||||||
|
|
||||||
|
if (this.controller) {
|
||||||
|
this.abortGetChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controller = controller;
|
||||||
|
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
||||||
|
// style=main_only returns only the current winning revision of the document
|
||||||
|
let url = `${this.url}/_changes?feed=continuous&style=main_only&heartbeat=${HEARTBEAT}`;
|
||||||
|
|
||||||
|
let body = {};
|
||||||
|
if (filter) {
|
||||||
|
url = `${url}&filter=_selector`;
|
||||||
|
body = JSON.stringify(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
signal,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": 'application/json'
|
||||||
|
},
|
||||||
|
body
|
||||||
|
});
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
let completed = false;
|
||||||
|
|
||||||
|
while (!completed) {
|
||||||
|
const {done, value} = await reader.read();
|
||||||
|
//done is true when we lose connection with the provider
|
||||||
|
if (done) {
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
let chunk = new Uint8Array(value.length);
|
||||||
|
chunk.set(value, 0);
|
||||||
|
const decodedChunk = new TextDecoder("utf-8").decode(chunk).split('\n');
|
||||||
|
if (decodedChunk.length && decodedChunk[decodedChunk.length - 1] === '') {
|
||||||
|
decodedChunk.forEach((doc, index) => {
|
||||||
|
try {
|
||||||
|
const object = JSON.parse(doc);
|
||||||
|
object.identifier = {
|
||||||
|
namespace: this.namespace,
|
||||||
|
key: object.id
|
||||||
|
};
|
||||||
|
let keyString = this.openmct.objects.makeKeyString(object.identifier);
|
||||||
|
let observersForObject = this.observers[keyString];
|
||||||
|
|
||||||
|
if (observersForObject) {
|
||||||
|
observersForObject.forEach(async (observer) => {
|
||||||
|
const updatedObject = await this.get(object.identifier);
|
||||||
|
observer(updatedObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//do nothing;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//We're done receiving from the provider. No more chunks.
|
||||||
|
intermediateResponse.resolve(true);
|
||||||
|
|
||||||
|
return intermediateResponse.promise;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
getIntermediateResponse() {
|
getIntermediateResponse() {
|
||||||
let intermediateResponse = {};
|
let intermediateResponse = {};
|
||||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||||
|
@ -24,8 +24,9 @@ import CouchObjectProvider from './CouchObjectProvider';
|
|||||||
const NAMESPACE = '';
|
const NAMESPACE = '';
|
||||||
const PERSISTENCE_SPACE = 'mct';
|
const PERSISTENCE_SPACE = 'mct';
|
||||||
|
|
||||||
export default function CouchPlugin(url) {
|
export default function CouchPlugin(options) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objects.addProvider(PERSISTENCE_SPACE, new CouchObjectProvider(openmct, url, NAMESPACE));
|
install.couchProvider = new CouchObjectProvider(openmct, options, NAMESPACE);
|
||||||
|
openmct.objects.addProvider(PERSISTENCE_SPACE, install.couchProvider);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,18 +32,42 @@ describe('the plugin', () => {
|
|||||||
let child;
|
let child;
|
||||||
let provider;
|
let provider;
|
||||||
let testPath = '/test/db';
|
let testPath = '/test/db';
|
||||||
|
let options;
|
||||||
|
let mockIdentifierService;
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: 'mct',
|
namespace: '',
|
||||||
key: 'some-value'
|
key: 'some-value'
|
||||||
},
|
},
|
||||||
type: 'mock-type'
|
type: 'mock-type'
|
||||||
};
|
};
|
||||||
|
options = {
|
||||||
|
url: testPath,
|
||||||
|
filter: {},
|
||||||
|
disableObserve: true
|
||||||
|
};
|
||||||
openmct = createOpenMct(false);
|
openmct = createOpenMct(false);
|
||||||
openmct.install(new CouchPlugin(testPath));
|
|
||||||
|
spyOnBuiltins(['fetch'], window);
|
||||||
|
|
||||||
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
|
'identifierService',
|
||||||
|
['parse']
|
||||||
|
);
|
||||||
|
mockIdentifierService.parse.and.returnValue({
|
||||||
|
getSpace: () => {
|
||||||
|
return 'mct';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||||
|
|
||||||
|
openmct.install(new CouchPlugin(options));
|
||||||
|
|
||||||
openmct.types.addType('mock-type', {creatable: true});
|
openmct.types.addType('mock-type', {creatable: true});
|
||||||
|
|
||||||
element = document.createElement('div');
|
element = document.createElement('div');
|
||||||
@ -57,9 +81,16 @@ describe('the plugin', () => {
|
|||||||
spyOn(provider, 'get').and.callThrough();
|
spyOn(provider, 'get').and.callThrough();
|
||||||
spyOn(provider, 'create').and.callThrough();
|
spyOn(provider, 'create').and.callThrough();
|
||||||
spyOn(provider, 'update').and.callThrough();
|
spyOn(provider, 'update').and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
spyOnBuiltins(['fetch'], window);
|
afterEach(() => {
|
||||||
fetch.and.returnValue(Promise.resolve({
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('the provider', () => {
|
||||||
|
let mockPromise;
|
||||||
|
beforeEach(() => {
|
||||||
|
mockPromise = Promise.resolve({
|
||||||
json: () => {
|
json: () => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -68,11 +99,8 @@ describe('the plugin', () => {
|
|||||||
model: {}
|
model: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
fetch.and.returnValue(mockPromise);
|
||||||
afterEach(() => {
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets an object', () => {
|
it('gets an object', () => {
|
||||||
@ -100,7 +128,7 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates queued objects', () => {
|
it('updates queued objects', () => {
|
||||||
let couchProvider = new CouchObjectProvider(openmct, 'http://localhost', '');
|
let couchProvider = new CouchObjectProvider(openmct, options, '');
|
||||||
let intermediateResponse = couchProvider.getIntermediateResponse();
|
let intermediateResponse = couchProvider.getIntermediateResponse();
|
||||||
spyOn(couchProvider, 'updateQueued');
|
spyOn(couchProvider, 'updateQueued');
|
||||||
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
||||||
@ -116,3 +144,4 @@ describe('the plugin', () => {
|
|||||||
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
@ -63,7 +63,8 @@ define([
|
|||||||
'./timeline/plugin',
|
'./timeline/plugin',
|
||||||
'./viewDatumAction/plugin',
|
'./viewDatumAction/plugin',
|
||||||
'./interceptors/plugin',
|
'./interceptors/plugin',
|
||||||
'./performanceIndicator/plugin'
|
'./performanceIndicator/plugin',
|
||||||
|
'./CouchDBSearchFolder/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -107,7 +108,8 @@ define([
|
|||||||
Timeline,
|
Timeline,
|
||||||
ViewDatumAction,
|
ViewDatumAction,
|
||||||
ObjectInterceptors,
|
ObjectInterceptors,
|
||||||
PerformanceIndicator
|
PerformanceIndicator,
|
||||||
|
CouchDBSearchFolder
|
||||||
) {
|
) {
|
||||||
const bundleMap = {
|
const bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
@ -206,6 +208,7 @@ define([
|
|||||||
plugins.ViewDatumAction = ViewDatumAction.default;
|
plugins.ViewDatumAction = ViewDatumAction.default;
|
||||||
plugins.ObjectInterceptors = ObjectInterceptors.default;
|
plugins.ObjectInterceptors = ObjectInterceptors.default;
|
||||||
plugins.PerformanceIndicator = PerformanceIndicator.default;
|
plugins.PerformanceIndicator = PerformanceIndicator.default;
|
||||||
|
plugins.CouchDBSearchFolder = CouchDBSearchFolder.default;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
@ -61,6 +61,11 @@ export default {
|
|||||||
this.openmct.time.on("timeSystem", this.setScaleAndPlotActivities);
|
this.openmct.time.on("timeSystem", this.setScaleAndPlotActivities);
|
||||||
this.openmct.time.on("bounds", this.updateViewBounds);
|
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
|
if (this.openmct.objects.supportsMutation(this.domainObject.identifier)) {
|
||||||
|
this.openmct.objects.getMutable(this.domainObject.identifier)
|
||||||
|
.then(this.observeForChanges);
|
||||||
|
}
|
||||||
|
|
||||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
@ -73,8 +78,10 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
observeForChanges(mutatedObject) {
|
observeForChanges(mutatedObject) {
|
||||||
|
if (mutatedObject.selectFile) {
|
||||||
this.validateJSON(mutatedObject.selectFile.body);
|
this.validateJSON(mutatedObject.selectFile.body);
|
||||||
this.setScaleAndPlotActivities();
|
this.setScaleAndPlotActivities();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resize() {
|
resize() {
|
||||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||||
|
@ -43,7 +43,7 @@ define([
|
|||||||
mutable = undefined;
|
mutable = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openmct.objects.supportsMutation(object)) {
|
if (openmct.objects.supportsMutation(object.identifier)) {
|
||||||
mutable = openmct.objects._toMutable(object);
|
mutable = openmct.objects._toMutable(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user