mirror of
https://github.com/nasa/openmct.git
synced 2025-06-11 20:01:41 +00:00
Merge branch 'open-master' into open644
Update with changes from the master branch to ensure changes for WTD-644 are compatible with these.
This commit is contained in:
@ -106,6 +106,37 @@ support for the category of extension being registered.
|
|||||||
list, with Angular-level dependencies are declared, and the full set
|
list, with Angular-level dependencies are declared, and the full set
|
||||||
is exposed as a single Angular "service."
|
is exposed as a single Angular "service."
|
||||||
|
|
||||||
|
### Priority order
|
||||||
|
|
||||||
|
Within each category, registration occurs in priority order. An extension's
|
||||||
|
priority may be specified as a `priority` property in its extension
|
||||||
|
definition; this may be a number, or a symbolic string. Extensions are
|
||||||
|
registered in reverse numeric order (highest-priority first), and symbolic
|
||||||
|
strings are mapped to the numeric values as follows:
|
||||||
|
|
||||||
|
* `fallback`: Negative infinity. Used for extensions that are not intended
|
||||||
|
for use (that is, they are meant to be overridden) but are present as an
|
||||||
|
option of last resort.
|
||||||
|
* `default`: -100. Used for extensions that are expected to be overridden, but
|
||||||
|
need a useful default.
|
||||||
|
* `none`: 0. Also used if no priority is specified, or if an unknown or
|
||||||
|
malformed priority is specified.
|
||||||
|
* `optional`: 100. Used for extensions that are meant to be used, but may be
|
||||||
|
overridden.
|
||||||
|
* `preferred`: 1000. Used for extensions that are specifically intended to
|
||||||
|
be used, but still may be overridden in principle.
|
||||||
|
* `mandatory`: Positive infinity. Used when an extension should definitely
|
||||||
|
not be overridden.
|
||||||
|
|
||||||
|
These symbolic names are chosen to reflect usage where many extensions may
|
||||||
|
satisfy a given usage, but only one may be used; in this case, as a
|
||||||
|
convention it should be the lowest-ordered (highest-priority) extensions
|
||||||
|
available. In other cases, a full set (or multi-element subset) of
|
||||||
|
extensions may be desired, with a specific ordering; in these cases, it
|
||||||
|
is preferable to specify priority numerically when declaring extensions,
|
||||||
|
and to understand that extensions will be sorted according to these
|
||||||
|
conventions when using them.
|
||||||
|
|
||||||
### Composite services
|
### Composite services
|
||||||
|
|
||||||
Composite services are assumed to follow a provider-aggregator-decorator
|
Composite services are assumed to follow a provider-aggregator-decorator
|
||||||
|
@ -16,5 +16,14 @@ define({
|
|||||||
"tests": "test",
|
"tests": "test",
|
||||||
"configuration": {},
|
"configuration": {},
|
||||||
"extensions": {}
|
"extensions": {}
|
||||||
}
|
},
|
||||||
|
PRIORITY_LEVELS: {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": 100,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
},
|
||||||
|
DEFAULT_PRIORITY: 0
|
||||||
});
|
});
|
@ -26,9 +26,11 @@ define(
|
|||||||
'./resolve/RequireConfigurator',
|
'./resolve/RequireConfigurator',
|
||||||
'./register/CustomRegistrars',
|
'./register/CustomRegistrars',
|
||||||
'./register/ExtensionRegistrar',
|
'./register/ExtensionRegistrar',
|
||||||
|
'./register/ExtensionSorter',
|
||||||
'./bootstrap/ApplicationBootstrapper'
|
'./bootstrap/ApplicationBootstrapper'
|
||||||
],
|
],
|
||||||
function (require,
|
function (
|
||||||
|
require,
|
||||||
es6promise,
|
es6promise,
|
||||||
angular,
|
angular,
|
||||||
angularRoute,
|
angularRoute,
|
||||||
@ -41,6 +43,7 @@ define(
|
|||||||
RequireConfigurator,
|
RequireConfigurator,
|
||||||
CustomRegistrars,
|
CustomRegistrars,
|
||||||
ExtensionRegistrar,
|
ExtensionRegistrar,
|
||||||
|
ExtensionSorter,
|
||||||
ApplicationBootstrapper
|
ApplicationBootstrapper
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -68,6 +71,7 @@ define(
|
|||||||
registrar = new ExtensionRegistrar(
|
registrar = new ExtensionRegistrar(
|
||||||
app,
|
app,
|
||||||
new CustomRegistrars(app, $log),
|
new CustomRegistrars(app, $log),
|
||||||
|
new ExtensionSorter($log),
|
||||||
$log
|
$log
|
||||||
),
|
),
|
||||||
bootstrapper = new ApplicationBootstrapper(
|
bootstrapper = new ApplicationBootstrapper(
|
||||||
|
@ -17,9 +17,11 @@ define(
|
|||||||
* @param {Object.<string,function>} customRegistrars an object
|
* @param {Object.<string,function>} customRegistrars an object
|
||||||
* containing custom registration functions, primarily for
|
* containing custom registration functions, primarily for
|
||||||
* Angular built-ins.
|
* Angular built-ins.
|
||||||
|
* @param {ExtensionSorter} sorter the sorter which will impose
|
||||||
|
* priority ordering upon extensions
|
||||||
* @param {*} $log Angular's logging service
|
* @param {*} $log Angular's logging service
|
||||||
*/
|
*/
|
||||||
function ExtensionRegistrar(app, customRegistrars, $log) {
|
function ExtensionRegistrar(app, customRegistrars, sorter, $log) {
|
||||||
// Track which extension categories have already been registered.
|
// Track which extension categories have already been registered.
|
||||||
// Exceptions will be thrown if the same extension category is
|
// Exceptions will be thrown if the same extension category is
|
||||||
// registered twice.
|
// registered twice.
|
||||||
@ -163,7 +165,7 @@ define(
|
|||||||
Object.keys(extensionGroup).forEach(function (category) {
|
Object.keys(extensionGroup).forEach(function (category) {
|
||||||
registerExtensionsForCategory(
|
registerExtensionsForCategory(
|
||||||
category,
|
category,
|
||||||
extensionGroup[category]
|
sorter.sort(extensionGroup[category])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
97
platform/framework/src/register/ExtensionSorter.js
Normal file
97
platform/framework/src/register/ExtensionSorter.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../Constants"],
|
||||||
|
function (Constants) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for applying priority order to extensions in a
|
||||||
|
* given category. This will sort in reverse order of the numeric
|
||||||
|
* priority given for extensions in the `priority` priority (such
|
||||||
|
* that large values are registered first.) Extensions may also
|
||||||
|
* specify symbolic properties as strings (instead of numbers),
|
||||||
|
* which will be looked up from the table `Constants.PRIORITY_LEVELS`.
|
||||||
|
* @param $log Angular's logging service
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ExtensionSorter($log) {
|
||||||
|
|
||||||
|
// Handle unknown or malformed priorities specified by extensions
|
||||||
|
function unrecognizedPriority(extension) {
|
||||||
|
// Issue a warning
|
||||||
|
$log.warn([
|
||||||
|
"Unrecognized priority '",
|
||||||
|
(extension || {}).priority,
|
||||||
|
"' specified for extension from ",
|
||||||
|
((extension || {}).bundle || {}).path,
|
||||||
|
"; defaulting to ",
|
||||||
|
Constants.DEFAULT_PRIORITY
|
||||||
|
].join(''));
|
||||||
|
|
||||||
|
// Provide a return value (default priority) to make this
|
||||||
|
// useful in an expression.
|
||||||
|
return Constants.DEFAULT_PRIORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPriority(extension) {
|
||||||
|
var priority =
|
||||||
|
(extension || {}).priority || Constants.DEFAULT_PRIORITY;
|
||||||
|
|
||||||
|
// If it's a symbolic priority, look it up
|
||||||
|
if (typeof priority === 'string') {
|
||||||
|
priority = Constants.PRIORITY_LEVELS[priority];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be a number; otherwise, issue a warning and
|
||||||
|
// fall back to default priority level.
|
||||||
|
return (typeof priority === 'number') ?
|
||||||
|
priority : unrecognizedPriority(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach a numeric priority to an extension; this is done in
|
||||||
|
// one pass outside of the comparator, mainly because getPriority
|
||||||
|
// may log warnings, and we only want this to happen once
|
||||||
|
// (instead of the many times that might occur during a sort.)
|
||||||
|
function prioritize(extension, index) {
|
||||||
|
return {
|
||||||
|
// The extension itself, for later unwrapping
|
||||||
|
extension: extension,
|
||||||
|
// The index, to provide a stable sort (see compare)
|
||||||
|
index: index,
|
||||||
|
// The numeric priority of the extension
|
||||||
|
priority: getPriority(extension)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap the original extension
|
||||||
|
// (for use after ordering has been applied)
|
||||||
|
function deprioritize(prioritized) {
|
||||||
|
return prioritized.extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two prioritized extensions
|
||||||
|
function compare(a, b) {
|
||||||
|
// Reverse order by numeric priority; or, original order.
|
||||||
|
return (b.priority - a.priority) || (a.index - b.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Sort extensions according to priority.
|
||||||
|
*
|
||||||
|
* @param {object[]} extensions array of resolved extensions
|
||||||
|
* @returns {object[]} the same extensions, in priority order
|
||||||
|
*/
|
||||||
|
sort: function (extensions) {
|
||||||
|
return (extensions || [])
|
||||||
|
.map(prioritize)
|
||||||
|
.sort(compare)
|
||||||
|
.map(deprioritize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtensionSorter;
|
||||||
|
}
|
||||||
|
);
|
@ -11,14 +11,24 @@ define(
|
|||||||
describe("The extension registrar", function () {
|
describe("The extension registrar", function () {
|
||||||
var mockApp,
|
var mockApp,
|
||||||
mockLog,
|
mockLog,
|
||||||
|
mockSorter,
|
||||||
customRegistrars,
|
customRegistrars,
|
||||||
registrar;
|
registrar;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockApp = jasmine.createSpyObj("app", ["factory"]);
|
mockApp = jasmine.createSpyObj("app", ["factory"]);
|
||||||
mockLog = jasmine.createSpyObj("$log", ["error", "warn", "debug", "info"]);
|
mockLog = jasmine.createSpyObj("$log", ["error", "warn", "debug", "info"]);
|
||||||
|
mockSorter = jasmine.createSpyObj("sorter", ["sort"]);
|
||||||
customRegistrars = {};
|
customRegistrars = {};
|
||||||
registrar = new ExtensionRegistrar(mockApp, customRegistrars, mockLog);
|
|
||||||
|
mockSorter.sort.andCallFake(function (v) { return v; });
|
||||||
|
|
||||||
|
registrar = new ExtensionRegistrar(
|
||||||
|
mockApp,
|
||||||
|
customRegistrars,
|
||||||
|
mockSorter,
|
||||||
|
mockLog
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers extensions using the factory", function () {
|
it("registers extensions using the factory", function () {
|
||||||
@ -64,6 +74,23 @@ define(
|
|||||||
expect(customRegistrars.things).toHaveBeenCalled();
|
expect(customRegistrars.things).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sorts extensions before registering", function () {
|
||||||
|
// Some extension definitions to sort
|
||||||
|
var a = { a: 'a' }, b = { b: 'b' }, c = { c: 'c' };
|
||||||
|
|
||||||
|
// Fake sorting; just reverse the array
|
||||||
|
mockSorter.sort.andCallFake(function (v) { return v.reverse(); });
|
||||||
|
|
||||||
|
// Register the extensions
|
||||||
|
registrar.registerExtensions({ things: [ a, b, c ] });
|
||||||
|
|
||||||
|
// Verify registration interactions occurred in reverse-order
|
||||||
|
[ c, b, a ].forEach(function (extension, index) {
|
||||||
|
expect(mockApp.factory.calls[index].args[1][0]())
|
||||||
|
.toEqual(extension);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
50
platform/framework/test/register/ExtensionSorterSpec.js
Normal file
50
platform/framework/test/register/ExtensionSorterSpec.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,jasmine,waitsFor*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/register/ExtensionSorter"],
|
||||||
|
function (ExtensionSorter) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The extension sorter", function () {
|
||||||
|
var mockLog,
|
||||||
|
sorter;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockLog = jasmine.createSpyObj(
|
||||||
|
"$log",
|
||||||
|
["error", "warn", "debug", "info"]
|
||||||
|
);
|
||||||
|
|
||||||
|
sorter = new ExtensionSorter(mockLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sorts extensions in priority order", function () {
|
||||||
|
var a = { priority: 10 },
|
||||||
|
b = {},
|
||||||
|
c = { priority: 'mandatory' }; // Should be +Inf
|
||||||
|
expect(sorter.sort([a, b, c])).toEqual([c, a, b]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns about unrecognized priorities", function () {
|
||||||
|
var a = { priority: 10 },
|
||||||
|
b = {},
|
||||||
|
c = { priority: 'mandatory' }, // Should be +Inf
|
||||||
|
d = { priority: 'GARBAGE-TEXT' },
|
||||||
|
e = { priority: { mal: "formed"} },
|
||||||
|
f = { priority: 3 };
|
||||||
|
|
||||||
|
// Sorting should use default order (note we assume
|
||||||
|
// a stable sort here as well)
|
||||||
|
expect(sorter.sort(
|
||||||
|
[a, b, c, d, e, f]
|
||||||
|
)).toEqual(
|
||||||
|
[c, a, f, b, d, e]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should have been warned exactly twice (for d & e)
|
||||||
|
expect(mockLog.warn.calls.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -6,6 +6,7 @@
|
|||||||
"load/Extension",
|
"load/Extension",
|
||||||
"register/CustomRegistrars",
|
"register/CustomRegistrars",
|
||||||
"register/ExtensionRegistrar",
|
"register/ExtensionRegistrar",
|
||||||
|
"register/ExtensionSorter",
|
||||||
"register/PartialConstructor",
|
"register/PartialConstructor",
|
||||||
"register/ServiceCompositor",
|
"register/ServiceCompositor",
|
||||||
"resolve/BundleResolver",
|
"resolve/BundleResolver",
|
||||||
|
Reference in New Issue
Block a user