[Root Objects] Order by specified priority (#4658)

* Updated objectAPI to support root priority
* Updated to new ES6 module for root registry and updated docs for new priority API and root object priority
* Set "My Items" to default priority of low, for root object order

Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Jamie V
2022-01-04 16:34:48 -08:00
committed by GitHub
parent d53ca3ec9a
commit 2fc0d34b8f
6 changed files with 179 additions and 105 deletions

46
API.md
View File

@@ -52,6 +52,8 @@
- [The URL Status Indicator](#the-url-status-indicator) - [The URL Status Indicator](#the-url-status-indicator)
- [Creating a Simple Indicator](#creating-a-simple-indicator) - [Creating a Simple Indicator](#creating-a-simple-indicator)
- [Custom Indicators](#custom-indicators) - [Custom Indicators](#custom-indicators)
- [Priority API](#priority-api)
- [Priority Types](#priority-types)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -247,16 +249,24 @@ To do so, use the `addRoot` method of the object API.
eg. eg.
```javascript ```javascript
openmct.objects.addRoot({ openmct.objects.addRoot({
namespace: "example.namespace", namespace: "example.namespace",
key: "my-key" key: "my-key"
}); },
openmct.priority.HIGH);
``` ```
The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers) The `addRoot` function takes a two arguments, the first can be an [object identifier](#domain-objects-and-identifiers) for a root level object, or an array of identifiers for root
as an argument. level objects, or a function that returns a promise for an identifier or an array of root level objects, the second is a [priority](#priority-api) or numeric value.
Root objects are loaded just like any other objects, i.e. via an object When using the `getAll` method of the object API, they will be returned in order of priority.
provider.
eg.
```javascript
openmct.objects.addRoot(identifier, openmct.priority.LOW); // low = -1000, will appear last in composition or tree
openmct.objects.addRoot(otherIdentifier, openmct.priority.HIGH); // high = 1000, will appear first in composition or tree
```
Root objects are loaded just like any other objects, i.e. via an object provider.
## Object Providers ## Object Providers
@@ -1051,3 +1061,25 @@ A completely custom indicator can be added by simply providing a DOM element to
element: domNode element: domNode
}); });
``` ```
## Priority API
Open MCT provides some built-in priority values that can be used in the application for view providers, indicators, root object order, and more.
### Priority Types
Currently, the Open MCT Priority API provides (type: numeric value):
- HIGH: 1000
- Default: 0
- LOW: -1000
View provider Example:
``` javascript
class ViewProvider {
...
priority() {
return openmct.priority.HIGH;
}
}
```

View File

@@ -41,7 +41,7 @@ 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(openmct);
this.inMemorySearchProvider = new InMemorySearchProvider(openmct); this.inMemorySearchProvider = new InMemorySearchProvider(openmct);
this.rootProvider = new RootObjectProvider(this.rootRegistry); this.rootProvider = new RootObjectProvider(this.rootRegistry);
@@ -367,14 +367,17 @@ ObjectAPI.prototype.endTransaction = function () {
/** /**
* Add a root-level object. * Add a root-level object.
* @param {module:openmct.ObjectAPI~Identifier|function} an array of * @param {module:openmct.ObjectAPI~Identifier|array|function} identifier an identifier or
* identifiers for root level objects, or a function that returns a * an array of identifiers for root level objects, or a function that returns a
* promise for an identifier or an array of root level objects. * promise for an identifier or an array of root level objects.
* @param {module:openmct.PriorityAPI~priority|Number} priority a number representing
* this item(s) position in the root object's composition (example: order in object tree).
* For arrays, they are treated as blocks.
* @method addRoot * @method addRoot
* @memberof module:openmct.ObjectAPI# * @memberof module:openmct.ObjectAPI#
*/ */
ObjectAPI.prototype.addRoot = function (key) { ObjectAPI.prototype.addRoot = function (identifier, priority) {
this.rootRegistry.addRoot(key); this.rootRegistry.addRoot(identifier, priority);
}; };
/** /**

View File

@@ -20,39 +20,43 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([ import utils from './object-utils';
'lodash'
], function (
_
) {
function RootRegistry() { export default class RootRegistry {
this.providers = [];
constructor(openmct) {
this._rootItems = [];
this._openmct = openmct;
} }
RootRegistry.prototype.getRoots = function () { getRoots() {
const promises = this.providers.map(function (provider) { const sortedItems = this._rootItems.sort((a, b) => b.priority - a.priority);
return provider(); const promises = sortedItems.map((rootItem) => rootItem.provider());
});
return Promise.all(promises) return Promise.all(promises).then(rootItems => rootItems.flat());
.then(_.flatten);
};
function isKey(key) {
return _.isObject(key) && _.has(key, 'key') && _.has(key, 'namespace');
} }
RootRegistry.prototype.addRoot = function (key) { addRoot(rootItem, priority) {
if (isKey(key) || (Array.isArray(key) && key.every(isKey))) {
this.providers.push(function () { if (!this._isValid(rootItem)) {
return key; return;
});
} else if (typeof key === "function") {
this.providers.push(key);
} }
};
return RootRegistry; this._rootItems.push({
priority: priority || this._openmct.priority.DEFAULT,
provider: typeof rootItem === 'function' ? rootItem : () => rootItem
});
}
}); _isValid(rootItem) {
if (utils.isIdentifier(rootItem) || typeof rootItem === 'function') {
return true;
}
if (Array.isArray(rootItem)) {
return rootItem.every(utils.isIdentifier);
}
return false;
}
}

View File

@@ -172,6 +172,7 @@ define([
} }
return { return {
isIdentifier: isIdentifier,
toOldFormat: toOldFormat, toOldFormat: toOldFormat,
toNewFormat: toNewFormat, toNewFormat: toNewFormat,
makeKeyString: makeKeyString, makeKeyString: makeKeyString,

View File

@@ -19,83 +19,113 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([
'../RootRegistry'
], function (
RootRegistry
) {
describe('RootRegistry', function () {
let idA;
let idB;
let idC;
let registry;
beforeEach(function () { import { createOpenMct, resetApplicationState } from '../../../utils/testing';
idA = {
key: 'keyA',
namespace: 'something'
};
idB = {
key: 'keyB',
namespace: 'something'
};
idC = {
key: 'keyC',
namespace: 'something'
};
registry = new RootRegistry();
});
it('can register a root by key', function () { describe('RootRegistry', () => {
registry.addRoot(idA); let openmct;
let idA;
let idB;
let idC;
let idD;
return registry.getRoots() beforeEach((done) => {
.then(function (roots) { openmct = createOpenMct();
expect(roots).toEqual([idA]); idA = {
}); key: 'keyA',
}); namespace: 'something'
};
idB = {
key: 'keyB',
namespace: 'something'
};
idC = {
key: 'keyC',
namespace: 'something'
};
idD = {
key: 'keyD',
namespace: 'something'
};
it('can register multiple roots by key', function () { openmct.on('start', done);
registry.addRoot([idA, idB]); openmct.startHeadless();
});
return registry.getRoots() afterEach(async () => {
.then(function (roots) { await resetApplicationState(openmct);
expect(roots).toEqual([idA, idB]); });
});
});
it('can register an asynchronous root ', function () { it('can register a root by identifier', () => {
registry.addRoot(function () { openmct.objects.addRoot(idA);
return Promise.resolve(idA);
return openmct.objects.getRoot()
.then((rootObject) => {
expect(rootObject.composition).toEqual([idA]);
}); });
});
return registry.getRoots() it('can register multiple roots by identifier', () => {
.then(function (roots) { openmct.objects.addRoot([idA, idB]);
expect(roots).toEqual([idA]);
});
});
it('can register multiple asynchronous roots', function () { return openmct.objects.getRoot()
registry.addRoot(function () { .then((rootObject) => {
return Promise.resolve([idA, idB]); expect(rootObject.composition).toEqual([idA, idB]);
}); });
});
return registry.getRoots() it('can register an asynchronous root ', () => {
.then(function (roots) { openmct.objects.addRoot(() => Promise.resolve(idA));
expect(roots).toEqual([idA, idB]);
});
});
it('can combine different types of registration', function () { return openmct.objects.getRoot()
registry.addRoot([idA, idB]); .then((rootObject) => {
registry.addRoot(function () { expect(rootObject.composition).toEqual([idA]);
return Promise.resolve([idC]);
}); });
});
return registry.getRoots() it('can register multiple asynchronous roots', () => {
.then(function (roots) { openmct.objects.addRoot(() => Promise.resolve([idA, idB]));
expect(roots).toEqual([idA, idB, idC]);
}); return openmct.objects.getRoot()
}); .then((rootObject) => {
expect(rootObject.composition).toEqual([idA, idB]);
});
});
it('can combine different types of registration', () => {
openmct.objects.addRoot([idA, idB]);
openmct.objects.addRoot(() => Promise.resolve([idC]));
return openmct.objects.getRoot()
.then((rootObject) => {
expect(rootObject.composition).toEqual([idA, idB, idC]);
});
});
it('supports priority ordering for identifiers', () => {
openmct.objects.addRoot(idA, openmct.priority.LOW);
openmct.objects.addRoot(idB, openmct.priority.HIGH);
openmct.objects.addRoot(idC); // DEFAULT
return openmct.objects.getRoot()
.then((rootObject) => {
expect(rootObject.composition[0]).toEqual(idB);
expect(rootObject.composition[1]).toEqual(idC);
expect(rootObject.composition[2]).toEqual(idA);
});
});
it('supports priority ordering for different types of registration', () => {
openmct.objects.addRoot(() => Promise.resolve([idC]), openmct.priority.LOW);
openmct.objects.addRoot(idB, openmct.priority.HIGH);
openmct.objects.addRoot([idA, idD]); // default
return openmct.objects.getRoot()
.then((rootObject) => {
expect(rootObject.composition[0]).toEqual(idB);
expect(rootObject.composition[1]).toEqual(idA);
expect(rootObject.composition[2]).toEqual(idD);
expect(rootObject.composition[3]).toEqual(idC);
});
}); });
}); });

View File

@@ -25,11 +25,15 @@ import myItemsInterceptor from "./myItemsInterceptor";
const MY_ITEMS_DEFAULT_NAME = 'My Items'; const MY_ITEMS_DEFAULT_NAME = 'My Items';
export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '') { export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '', priority = undefined) {
return function install(openmct) { return function install(openmct) {
const identifier = createMyItemsIdentifier(namespace); const identifier = createMyItemsIdentifier(namespace);
if (priority === undefined) {
priority = openmct.priority.LOW;
}
openmct.objects.addGetInterceptor(myItemsInterceptor(openmct, identifier, name)); openmct.objects.addGetInterceptor(myItemsInterceptor(openmct, identifier, name));
openmct.objects.addRoot(identifier); openmct.objects.addRoot(identifier, priority);
}; };
} }