mirror of
https://github.com/nasa/openmct.git
synced 2025-06-03 16:10:54 +00:00
Add object interceptor API to allow missing model and missing my-items handling (#3522)
* Extends Object API to allow adding interceptors Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
176226ddef
commit
9da750c3bb
@ -282,6 +282,7 @@ define([
|
|||||||
this.install(this.plugins.NotificationIndicator());
|
this.install(this.plugins.NotificationIndicator());
|
||||||
this.install(this.plugins.NewFolderAction());
|
this.install(this.plugins.NewFolderAction());
|
||||||
this.install(this.plugins.ViewDatumAction());
|
this.install(this.plugins.ViewDatumAction());
|
||||||
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
66
src/api/objects/InterceptorRegistry.js
Normal file
66
src/api/objects/InterceptorRegistry.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
export default class InterceptorRegistry {
|
||||||
|
/**
|
||||||
|
* A InterceptorRegistry maintains the definitions for different interceptors that may be invoked on domain objects.
|
||||||
|
* @interface InterceptorRegistry
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.interceptors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface InterceptorDef
|
||||||
|
* @property {function} appliesTo function that determines if this interceptor should be called for the given identifier/object
|
||||||
|
* @property {function} invoke function that transforms the provided domain object and returns the transformed domain object
|
||||||
|
* @property {function} priority the priority for this interceptor. A higher number returned has more weight than a lower number
|
||||||
|
* @memberof module:openmct InterceptorRegistry#
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new object interceptor.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.InterceptorDef} interceptorDef the interceptor to add
|
||||||
|
* @method addInterceptor
|
||||||
|
* @memberof module:openmct.InterceptorRegistry#
|
||||||
|
*/
|
||||||
|
addInterceptor(interceptorDef) {
|
||||||
|
//TODO: sort by priority
|
||||||
|
this.interceptors.push(interceptorDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all interceptors applicable to a domain object.
|
||||||
|
* @method getInterceptors
|
||||||
|
* @returns [module:openmct.InterceptorDef] the registered interceptors for this identifier/object
|
||||||
|
* @memberof module:openmct.InterceptorRegistry#
|
||||||
|
*/
|
||||||
|
getInterceptors(identifier, object) {
|
||||||
|
return this.interceptors.filter(interceptor => {
|
||||||
|
return typeof interceptor.appliesTo === 'function'
|
||||||
|
&& interceptor.appliesTo(identifier, object);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
0
src/api/objects/InterceptorRegistrySpec.js
Normal file
0
src/api/objects/InterceptorRegistrySpec.js
Normal file
@ -26,6 +26,7 @@ define([
|
|||||||
'./MutableObject',
|
'./MutableObject',
|
||||||
'./RootRegistry',
|
'./RootRegistry',
|
||||||
'./RootObjectProvider',
|
'./RootObjectProvider',
|
||||||
|
'./InterceptorRegistry',
|
||||||
'EventEmitter'
|
'EventEmitter'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
@ -33,6 +34,7 @@ define([
|
|||||||
MutableObject,
|
MutableObject,
|
||||||
RootRegistry,
|
RootRegistry,
|
||||||
RootObjectProvider,
|
RootObjectProvider,
|
||||||
|
InterceptorRegistry,
|
||||||
EventEmitter
|
EventEmitter
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ define([
|
|||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
|
this.interceptorRegistry = new InterceptorRegistry.default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,6 +180,10 @@ define([
|
|||||||
|
|
||||||
return objectPromise.then(result => {
|
return objectPromise.then(result => {
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
|
const interceptors = this.listGetInterceptors(identifier, result);
|
||||||
|
interceptors.forEach(interceptor => {
|
||||||
|
result = interceptor.invoke(identifier, result);
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@ -312,6 +319,27 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an object interceptor that transforms a domain object requested via module:openmct.ObjectAPI.get
|
||||||
|
* The domain object will be transformed after it is retrieved from the persistence store
|
||||||
|
* The domain object will be transformed only if the interceptor is applicable to that domain object as defined by the InterceptorDef
|
||||||
|
*
|
||||||
|
* @param {module:openmct.InterceptorDef} interceptorDef the interceptor definition to add
|
||||||
|
* @method addGetInterceptor
|
||||||
|
* @memberof module:openmct.InterceptorRegistry#
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.addGetInterceptor = function (interceptorDef) {
|
||||||
|
this.interceptorRegistry.addInterceptor(interceptorDef);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the interceptors for a given domain object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.listGetInterceptors = function (identifier, object) {
|
||||||
|
return this.interceptorRegistry.getInterceptors(identifier, object);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uniquely identifies a domain object.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
|
@ -63,12 +63,51 @@ describe("The Object API", () => {
|
|||||||
describe("The get function", () => {
|
describe("The get function", () => {
|
||||||
describe("when a provider is available", () => {
|
describe("when a provider is available", () => {
|
||||||
let mockProvider;
|
let mockProvider;
|
||||||
|
let mockInterceptor;
|
||||||
|
let anotherMockInterceptor;
|
||||||
|
let notApplicableMockInterceptor;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||||
"get"
|
"get"
|
||||||
]);
|
]);
|
||||||
mockProvider.get.and.returnValue(Promise.resolve(mockDomainObject));
|
mockProvider.get.and.returnValue(Promise.resolve(mockDomainObject));
|
||||||
|
|
||||||
|
mockInterceptor = jasmine.createSpyObj("mock interceptor", [
|
||||||
|
"appliesTo",
|
||||||
|
"invoke"
|
||||||
|
]);
|
||||||
|
mockInterceptor.appliesTo.and.returnValue(true);
|
||||||
|
mockInterceptor.invoke.and.callFake((identifier, object) => {
|
||||||
|
return Object.assign({
|
||||||
|
changed: true
|
||||||
|
}, object);
|
||||||
|
});
|
||||||
|
|
||||||
|
anotherMockInterceptor = jasmine.createSpyObj("another mock interceptor", [
|
||||||
|
"appliesTo",
|
||||||
|
"invoke"
|
||||||
|
]);
|
||||||
|
anotherMockInterceptor.appliesTo.and.returnValue(true);
|
||||||
|
anotherMockInterceptor.invoke.and.callFake((identifier, object) => {
|
||||||
|
return Object.assign({
|
||||||
|
alsoChanged: true
|
||||||
|
}, object);
|
||||||
|
});
|
||||||
|
|
||||||
|
notApplicableMockInterceptor = jasmine.createSpyObj("not applicable mock interceptor", [
|
||||||
|
"appliesTo",
|
||||||
|
"invoke"
|
||||||
|
]);
|
||||||
|
notApplicableMockInterceptor.appliesTo.and.returnValue(false);
|
||||||
|
notApplicableMockInterceptor.invoke.and.callFake((identifier, object) => {
|
||||||
|
return Object.assign({
|
||||||
|
shouldNotBeChanged: true
|
||||||
|
}, object);
|
||||||
|
});
|
||||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
|
objectAPI.addGetInterceptor(mockInterceptor);
|
||||||
|
objectAPI.addGetInterceptor(anotherMockInterceptor);
|
||||||
|
objectAPI.addGetInterceptor(notApplicableMockInterceptor);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Caches multiple requests for the same object", () => {
|
it("Caches multiple requests for the same object", () => {
|
||||||
@ -78,6 +117,15 @@ describe("The Object API", () => {
|
|||||||
objectAPI.get(mockDomainObject.identifier);
|
objectAPI.get(mockDomainObject.identifier);
|
||||||
expect(mockProvider.get.calls.count()).toBe(1);
|
expect(mockProvider.get.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("applies any applicable interceptors", () => {
|
||||||
|
expect(mockDomainObject.changed).toBeUndefined();
|
||||||
|
objectAPI.get(mockDomainObject.identifier).then((object) => {
|
||||||
|
expect(object.changed).toBeTrue();
|
||||||
|
expect(object.alsoChanged).toBeTrue();
|
||||||
|
expect(object.shouldNotBeChanged).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
40
src/plugins/interceptors/missingObjectInterceptor.js
Normal file
40
src/plugins/interceptors/missingObjectInterceptor.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export default function MissingObjectInterceptor(openmct) {
|
||||||
|
openmct.objects.addGetInterceptor({
|
||||||
|
appliesTo: (identifier, domainObject) => {
|
||||||
|
return identifier.key !== 'mine';
|
||||||
|
},
|
||||||
|
invoke: (identifier, object) => {
|
||||||
|
if (object === undefined) {
|
||||||
|
return {
|
||||||
|
identifier,
|
||||||
|
type: 'unknown',
|
||||||
|
name: 'Missing: ' + openmct.objects.makeKeyString(identifier)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
43
src/plugins/interceptors/myItemsInterceptor.js
Normal file
43
src/plugins/interceptors/myItemsInterceptor.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export default function MyItemsInterceptor(openmct) {
|
||||||
|
|
||||||
|
openmct.objects.addGetInterceptor({
|
||||||
|
appliesTo: (identifier, domainObject) => {
|
||||||
|
return identifier.key === 'mine';
|
||||||
|
},
|
||||||
|
invoke: (identifier, object) => {
|
||||||
|
if (object === undefined) {
|
||||||
|
return {
|
||||||
|
identifier,
|
||||||
|
"name": "My Items",
|
||||||
|
"type": "folder",
|
||||||
|
"composition": [],
|
||||||
|
"location": "ROOT"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
9
src/plugins/interceptors/plugin.js
Normal file
9
src/plugins/interceptors/plugin.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import missingObjectInterceptor from "./missingObjectInterceptor";
|
||||||
|
import myItemsInterceptor from "./myItemsInterceptor";
|
||||||
|
|
||||||
|
export default function plugin() {
|
||||||
|
return function install(openmct) {
|
||||||
|
myItemsInterceptor(openmct);
|
||||||
|
missingObjectInterceptor(openmct);
|
||||||
|
};
|
||||||
|
}
|
@ -87,7 +87,8 @@ export default class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
||||||
if (!this.objectQueue[key].pending) {
|
//Only update the rev if it's the first time we're getting the object from CouchDB. Subsequent revs should only be updated by updates.
|
||||||
|
if (!this.objectQueue[key].pending && !this.objectQueue[key].rev) {
|
||||||
this.objectQueue[key].updateRevision(response[REV]);
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,8 @@ define([
|
|||||||
'./persistence/couch/plugin',
|
'./persistence/couch/plugin',
|
||||||
'./defaultRootName/plugin',
|
'./defaultRootName/plugin',
|
||||||
'./timeline/plugin',
|
'./timeline/plugin',
|
||||||
'./viewDatumAction/plugin'
|
'./viewDatumAction/plugin',
|
||||||
|
'./interceptors/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -99,7 +100,8 @@ define([
|
|||||||
CouchDBPlugin,
|
CouchDBPlugin,
|
||||||
DefaultRootName,
|
DefaultRootName,
|
||||||
Timeline,
|
Timeline,
|
||||||
ViewDatumAction
|
ViewDatumAction,
|
||||||
|
ObjectInterceptors
|
||||||
) {
|
) {
|
||||||
const bundleMap = {
|
const bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
@ -194,6 +196,7 @@ define([
|
|||||||
plugins.DefaultRootName = DefaultRootName.default;
|
plugins.DefaultRootName = DefaultRootName.default;
|
||||||
plugins.Timeline = Timeline.default;
|
plugins.Timeline = Timeline.default;
|
||||||
plugins.ViewDatumAction = ViewDatumAction.default;
|
plugins.ViewDatumAction = ViewDatumAction.default;
|
||||||
|
plugins.ObjectInterceptors = ObjectInterceptors.default;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user