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:
Shefali Joshi
2021-02-22 18:35:11 -08:00
committed by GitHub
parent dd3d4c8c3a
commit 29128a891d
14 changed files with 513 additions and 68 deletions

View File

@ -196,7 +196,7 @@ define([
this.provider.add(this.domainObject, child.identifier);
} 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);
child = this.publicAPI.objects._toMutable(child);

View File

@ -96,6 +96,16 @@ class MutableDomainObject {
//TODO: Emit events for listeners of child properties when parent changes.
// 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) {
this._instanceEventEmitter.on(event, callback);

View File

@ -33,11 +33,15 @@ import InterceptorRegistry from './InterceptorRegistry';
* @memberof module:openmct
*/
function ObjectAPI(typeRegistry) {
function ObjectAPI(typeRegistry, openmct) {
this.typeRegistry = typeRegistry;
this.eventEmitter = new EventEmitter();
this.providers = {};
this.rootRegistry = new RootRegistry();
this.injectIdentifierService = function () {
this.identifierService = openmct.$injector.get("identifierService");
};
this.rootProvider = new RootObjectProvider(this.rootRegistry);
this.cache = {};
this.interceptorRegistry = new InterceptorRegistry();
@ -51,16 +55,33 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (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.
* @private
*/
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') {
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
* the object can be mutated.
*/
ObjectAPI.prototype.getMutable = function (identifier) {
if (!this.supportsMutation(identifier)) {
throw new Error(`Object "${this.makeKeyString(identifier)}" does not support mutation.`);
ObjectAPI.prototype.getMutable = function (idOrKeyString) {
if (!this.supportsMutation(idOrKeyString)) {
throw new Error(`Object "${this.makeKeyString(idOrKeyString)}" does not support mutation.`);
}
return this.get(identifier).then((object) => {
return this._toMutable(object);
return this.get(idOrKeyString).then((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;
});
};

View File

@ -3,6 +3,8 @@ import ObjectAPI from './ObjectAPI.js';
describe("The Object API", () => {
let objectAPI;
let typeRegistry;
let openmct = {};
let mockIdentifierService;
let mockDomainObject;
const TEST_NAMESPACE = "test-namespace";
const FIFTEEN_MINUTES = 15 * 60 * 1000;
@ -11,7 +13,19 @@ describe("The Object API", () => {
typeRegistry = jasmine.createSpyObj('typeRegistry', [
'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 = {
identifier: {
namespace: TEST_NAMESPACE,
@ -136,11 +150,13 @@ describe("The Object API", () => {
describe("the mutation API", () => {
let testObject;
let updatedTestObject;
let mutable;
let mockProvider;
let callbacks = [];
beforeEach(function () {
objectAPI = new ObjectAPI(typeRegistry);
objectAPI = new ObjectAPI(typeRegistry, openmct);
testObject = {
identifier: {
namespace: TEST_NAMESPACE,
@ -154,12 +170,27 @@ describe("The Object API", () => {
}
}
};
updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
mockProvider = jasmine.createSpyObj("mock provider", [
"get",
"create",
"update"
"update",
"observe",
"observeObjectChanges"
]);
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);
return objectAPI.getMutable(testObject.identifier)
@ -191,6 +222,13 @@ describe("The Object API", () => {
it('that is identical to original object when serialized', function () {
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 () {