mirror of
https://github.com/nasa/openmct.git
synced 2024-12-30 09:58:52 +00:00
Merge remote-tracking branch 'github-open/open141' into open141-integration
This commit is contained in:
commit
fc0bfa77db
@ -4,6 +4,10 @@
|
||||
{
|
||||
"implementation": "WatchIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
},
|
||||
{
|
||||
"implementation": "DigestIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
77
example/profiling/src/DigestIndicator.js
Normal file
77
example/profiling/src/DigestIndicator.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Displays the number of digests that have occurred since the
|
||||
* indicator was first instantiated.
|
||||
* @constructor
|
||||
* @param $interval Angular's $interval
|
||||
* @implements {Indicator}
|
||||
*/
|
||||
function DigestIndicator($interval, $rootScope) {
|
||||
var digests = 0,
|
||||
displayed = 0,
|
||||
start = Date.now();
|
||||
|
||||
function update() {
|
||||
var secs = (Date.now() - start) / 1000;
|
||||
displayed = Math.round(digests / secs);
|
||||
}
|
||||
|
||||
function increment() {
|
||||
digests += 1;
|
||||
}
|
||||
|
||||
$rootScope.$watch(increment);
|
||||
|
||||
// Update state every second
|
||||
$interval(update, 1000);
|
||||
|
||||
// Provide initial state, too
|
||||
update();
|
||||
|
||||
return {
|
||||
getGlyph: function () {
|
||||
return ".";
|
||||
},
|
||||
getGlyphClass: function () {
|
||||
return undefined;
|
||||
},
|
||||
getText: function () {
|
||||
return displayed + " digests/sec";
|
||||
},
|
||||
getDescription: function () {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return DigestIndicator;
|
||||
|
||||
}
|
||||
);
|
@ -29,7 +29,8 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var TOPIC_PREFIX = "mutation:";
|
||||
var GENERAL_TOPIC = "mutation",
|
||||
TOPIC_PREFIX = "mutation:";
|
||||
|
||||
// Utility function to overwrite a destination object
|
||||
// with the contents of a source object.
|
||||
@ -78,7 +79,11 @@ define(
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function MutationCapability(topic, now, domainObject) {
|
||||
this.mutationTopic = topic(TOPIC_PREFIX + domainObject.getId());
|
||||
this.generalMutationTopic =
|
||||
topic(GENERAL_TOPIC);
|
||||
this.specificMutationTopic =
|
||||
topic(TOPIC_PREFIX + domainObject.getId());
|
||||
|
||||
this.now = now;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
@ -115,11 +120,17 @@ define(
|
||||
// mutator function has a temporary copy to work with.
|
||||
var domainObject = this.domainObject,
|
||||
now = this.now,
|
||||
t = this.mutationTopic,
|
||||
generalTopic = this.generalMutationTopic,
|
||||
specificTopic = this.specificMutationTopic,
|
||||
model = domainObject.getModel(),
|
||||
clone = JSON.parse(JSON.stringify(model)),
|
||||
useTimestamp = arguments.length > 1;
|
||||
|
||||
function notifyListeners(model) {
|
||||
generalTopic.notify(domainObject);
|
||||
specificTopic.notify(model);
|
||||
}
|
||||
|
||||
// Function to handle copying values to the actual
|
||||
function handleMutation(mutationResult) {
|
||||
// If mutation result was undefined, just use
|
||||
@ -136,7 +147,7 @@ define(
|
||||
copyValues(model, result);
|
||||
}
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
t.notify(model);
|
||||
notifyListeners(model);
|
||||
}
|
||||
|
||||
// Report the result of the mutation
|
||||
@ -158,7 +169,7 @@ define(
|
||||
* @memberof platform/core.MutationCapability#
|
||||
*/
|
||||
MutationCapability.prototype.listen = function (listener) {
|
||||
return this.mutationTopic.listen(listener);
|
||||
return this.specificMutationTopic.listen(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -61,7 +61,7 @@ define(
|
||||
* @memberof platform/core.Throttle#
|
||||
*/
|
||||
return function (fn, delay, apply) {
|
||||
var promise, // Promise for the result of throttled function
|
||||
var promise,
|
||||
args = [];
|
||||
|
||||
function invoke() {
|
||||
|
@ -45,7 +45,15 @@
|
||||
"provides": "searchService",
|
||||
"type": "provider",
|
||||
"implementation": "services/GenericSearchProvider.js",
|
||||
"depends": [ "$q", "$timeout", "objectService", "workerService", "GENERIC_SEARCH_ROOTS" ]
|
||||
"depends": [
|
||||
"$q",
|
||||
"$log",
|
||||
"throttle",
|
||||
"objectService",
|
||||
"workerService",
|
||||
"topic",
|
||||
"GENERIC_SEARCH_ROOTS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"provides": "searchService",
|
||||
|
@ -31,6 +31,8 @@ define(
|
||||
|
||||
var DEFAULT_MAX_RESULTS = 100,
|
||||
DEFAULT_TIMEOUT = 1000,
|
||||
MAX_CONCURRENT_REQUESTS = 100,
|
||||
FLUSH_INTERVAL = 0,
|
||||
stopTime;
|
||||
|
||||
/**
|
||||
@ -39,7 +41,8 @@ define(
|
||||
*
|
||||
* @constructor
|
||||
* @param $q Angular's $q, for promise consolidation.
|
||||
* @param $timeout Angular's $timeout, for delayed function execution.
|
||||
* @param $log Anglar's $log, for logging.
|
||||
* @param {Function} throttle a function to throttle function invocations
|
||||
* @param {ObjectService} objectService The service from which
|
||||
* domain objects can be gotten.
|
||||
* @param {WorkerService} workerService The service which allows
|
||||
@ -47,10 +50,16 @@ define(
|
||||
* @param {GENERIC_SEARCH_ROOTS} ROOTS An array of the root
|
||||
* domain objects' IDs.
|
||||
*/
|
||||
function GenericSearchProvider($q, $timeout, objectService, workerService, ROOTS) {
|
||||
function GenericSearchProvider($q, $log, throttle, objectService, workerService, topic, ROOTS) {
|
||||
var indexed = {},
|
||||
pendingIndex = {},
|
||||
pendingQueries = {},
|
||||
worker = workerService.run('genericSearchWorker');
|
||||
toRequest = [],
|
||||
worker = workerService.run('genericSearchWorker'),
|
||||
mutationTopic = topic("mutation"),
|
||||
indexingStarted = Date.now(),
|
||||
pendingRequests = 0,
|
||||
scheduleFlush;
|
||||
|
||||
this.worker = worker;
|
||||
this.pendingQueries = pendingQueries;
|
||||
@ -58,23 +67,31 @@ define(
|
||||
// pendingQueries is a dictionary with the key value pairs st
|
||||
// the key is the timestamp and the value is the promise
|
||||
|
||||
// Tell the web worker to add a domain object's model to its list of items.
|
||||
function indexItem(domainObject) {
|
||||
var message;
|
||||
|
||||
// undefined check
|
||||
if (domainObject && domainObject.getModel) {
|
||||
// Using model instead of whole domain object because
|
||||
// it's a JSON object.
|
||||
message = {
|
||||
request: 'index',
|
||||
model: domainObject.getModel(),
|
||||
id: domainObject.getId()
|
||||
};
|
||||
worker.postMessage(message);
|
||||
}
|
||||
function scheduleIdsForIndexing(ids) {
|
||||
ids.forEach(function (id) {
|
||||
if (!indexed[id] && !pendingIndex[id]) {
|
||||
indexed[id] = true;
|
||||
pendingIndex[id] = true;
|
||||
toRequest.push(id);
|
||||
}
|
||||
});
|
||||
scheduleFlush();
|
||||
}
|
||||
|
||||
// Tell the web worker to add a domain object's model to its list of items.
|
||||
function indexItem(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
|
||||
worker.postMessage({
|
||||
request: 'index',
|
||||
model: model,
|
||||
id: domainObject.getId()
|
||||
});
|
||||
|
||||
if (Array.isArray(model.composition)) {
|
||||
scheduleIdsForIndexing(model.composition);
|
||||
}
|
||||
}
|
||||
|
||||
// Handles responses from the web worker. Namely, the results of
|
||||
// a search request.
|
||||
@ -111,82 +128,48 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for getItems(). Indexes the tree.
|
||||
function indexItems(nodes) {
|
||||
nodes.forEach(function (node) {
|
||||
var id = node && node.getId && node.getId();
|
||||
|
||||
// If we have already indexed this item, stop here
|
||||
if (indexed[id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Index each item with the web worker
|
||||
indexItem(node);
|
||||
indexed[id] = true;
|
||||
|
||||
|
||||
// If this node has children, index those
|
||||
if (node && node.hasCapability && node.hasCapability('composition')) {
|
||||
// Make sure that this is async, so doesn't block up page
|
||||
$timeout(function () {
|
||||
// Get the children...
|
||||
node.useCapability('composition').then(function (children) {
|
||||
$timeout(function () {
|
||||
// ... then index the children
|
||||
if (children.constructor === Array) {
|
||||
indexItems(children);
|
||||
} else {
|
||||
indexItems([children]);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Watch for changes to this item, in case it gets new children
|
||||
if (node && node.hasCapability && node.hasCapability('mutation')) {
|
||||
node.getCapability('mutation').listen(function (listener) {
|
||||
if (listener && listener.composition) {
|
||||
// If the node was mutated to have children, get the child domain objects
|
||||
objectService.getObjects(listener.composition).then(function (objectsById) {
|
||||
var objects = [],
|
||||
id;
|
||||
|
||||
// Get each of the domain objects in objectsById
|
||||
for (id in objectsById) {
|
||||
objects.push(objectsById[id]);
|
||||
}
|
||||
|
||||
indexItems(objects);
|
||||
});
|
||||
}
|
||||
});
|
||||
function requestAndIndex(id) {
|
||||
pendingRequests += 1;
|
||||
objectService.getObjects([id]).then(function (objects) {
|
||||
delete pendingIndex[id];
|
||||
if (objects[id]) {
|
||||
indexItem(objects[id]);
|
||||
}
|
||||
}, function () {
|
||||
$log.warn("Failed to index domain object " + id);
|
||||
}).then(function () {
|
||||
pendingRequests -= 1;
|
||||
scheduleFlush();
|
||||
});
|
||||
}
|
||||
|
||||
// Converts the filetree into a list
|
||||
function getItems() {
|
||||
// Aquire root objects
|
||||
objectService.getObjects(ROOTS).then(function (objectsById) {
|
||||
var objects = [],
|
||||
id;
|
||||
scheduleFlush = throttle(function flush() {
|
||||
var batchSize =
|
||||
Math.max(MAX_CONCURRENT_REQUESTS - pendingRequests, 0);
|
||||
|
||||
// Get each of the domain objects in objectsById
|
||||
for (id in objectsById) {
|
||||
objects.push(objectsById[id]);
|
||||
}
|
||||
|
||||
// Index all of the roots' descendents
|
||||
indexItems(objects);
|
||||
});
|
||||
}
|
||||
if (toRequest.length + pendingRequests < 1) {
|
||||
$log.info([
|
||||
'GenericSearch finished indexing after ',
|
||||
((Date.now() - indexingStarted) / 1000).toFixed(2),
|
||||
' seconds.'
|
||||
].join(''));
|
||||
} else {
|
||||
toRequest.splice(-batchSize, batchSize)
|
||||
.forEach(requestAndIndex);
|
||||
}
|
||||
}, FLUSH_INTERVAL);
|
||||
|
||||
worker.onmessage = handleResponse;
|
||||
|
||||
// Index the tree's contents once at the beginning
|
||||
getItems();
|
||||
scheduleIdsForIndexing(ROOTS);
|
||||
|
||||
// Re-index items when they are mutated
|
||||
mutationTopic.listen(function (domainObject) {
|
||||
var id = domainObject.getId();
|
||||
indexed[id] = false;
|
||||
scheduleIdsForIndexing([id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,26 +31,56 @@ define(
|
||||
|
||||
describe("The generic search provider ", function () {
|
||||
var mockQ,
|
||||
mockTimeout,
|
||||
mockLog,
|
||||
mockThrottle,
|
||||
mockDeferred,
|
||||
mockObjectService,
|
||||
mockObjectPromise,
|
||||
mockChainedPromise,
|
||||
mockDomainObjects,
|
||||
mockCapability,
|
||||
mockCapabilityPromise,
|
||||
mockWorkerService,
|
||||
mockWorker,
|
||||
mockTopic,
|
||||
mockMutationTopic,
|
||||
mockRoots = ['root1', 'root2'],
|
||||
mockThrottledFn,
|
||||
throttledCallCount,
|
||||
provider,
|
||||
mockProviderResults;
|
||||
|
||||
beforeEach(function () {
|
||||
function resolveObjectPromises() {
|
||||
var i;
|
||||
for (i = 0; i < mockObjectPromise.then.calls.length; i += 1) {
|
||||
mockChainedPromise.then.calls[i].args[0](
|
||||
mockObjectPromise.then.calls[i]
|
||||
.args[0](mockDomainObjects)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveThrottledFn() {
|
||||
if (mockThrottledFn.calls.length > throttledCallCount) {
|
||||
mockThrottle.mostRecentCall.args[0]();
|
||||
throttledCallCount = mockThrottledFn.calls.length;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAsyncTasks() {
|
||||
resolveThrottledFn();
|
||||
resolveObjectPromises();
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj(
|
||||
"$q",
|
||||
[ "defer" ]
|
||||
);
|
||||
mockLog = jasmine.createSpyObj(
|
||||
"$log",
|
||||
[ "error", "warn", "info", "debug" ]
|
||||
);
|
||||
mockDeferred = jasmine.createSpyObj(
|
||||
"deferred",
|
||||
[ "resolve", "reject"]
|
||||
@ -58,7 +88,9 @@ define(
|
||||
mockDeferred.promise = "mock promise";
|
||||
mockQ.defer.andReturn(mockDeferred);
|
||||
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockThrottle = jasmine.createSpy("throttle");
|
||||
mockThrottledFn = jasmine.createSpy("throttledFn");
|
||||
throttledCallCount = 0;
|
||||
|
||||
mockObjectService = jasmine.createSpyObj(
|
||||
"objectService",
|
||||
@ -68,8 +100,13 @@ define(
|
||||
"promise",
|
||||
[ "then", "catch" ]
|
||||
);
|
||||
mockChainedPromise = jasmine.createSpyObj(
|
||||
"chainedPromise",
|
||||
[ "then" ]
|
||||
);
|
||||
mockObjectService.getObjects.andReturn(mockObjectPromise);
|
||||
|
||||
mockTopic = jasmine.createSpy('topic');
|
||||
|
||||
mockWorkerService = jasmine.createSpyObj(
|
||||
"workerService",
|
||||
@ -87,59 +124,100 @@ define(
|
||||
);
|
||||
|
||||
mockDomainObjects = {};
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
mockDomainObjects[i] = (
|
||||
['a', 'root1', 'root2'].forEach(function (id) {
|
||||
mockDomainObjects[id] = (
|
||||
jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getModel", "hasCapability", "getCapability", "useCapability" ]
|
||||
[
|
||||
"getId",
|
||||
"getModel",
|
||||
"hasCapability",
|
||||
"getCapability",
|
||||
"useCapability"
|
||||
]
|
||||
)
|
||||
);
|
||||
mockDomainObjects[i].getId.andReturn(i);
|
||||
mockDomainObjects[i].getCapability.andReturn(mockCapability);
|
||||
mockDomainObjects[i].useCapability.andReturn(mockCapabilityPromise);
|
||||
}
|
||||
// Give the first object children
|
||||
mockDomainObjects[0].hasCapability.andReturn(true);
|
||||
mockDomainObjects[id].getId.andReturn(id);
|
||||
mockDomainObjects[id].getCapability.andReturn(mockCapability);
|
||||
mockDomainObjects[id].useCapability.andReturn(mockCapabilityPromise);
|
||||
mockDomainObjects[id].getModel.andReturn({});
|
||||
});
|
||||
|
||||
mockCapability = jasmine.createSpyObj(
|
||||
"capability",
|
||||
[ "invoke", "listen" ]
|
||||
);
|
||||
mockCapability.invoke.andReturn(mockCapabilityPromise);
|
||||
mockDomainObjects[0].getCapability.andReturn(mockCapability);
|
||||
mockDomainObjects.a.getCapability.andReturn(mockCapability);
|
||||
mockMutationTopic = jasmine.createSpyObj(
|
||||
'mutationTopic',
|
||||
[ 'listen' ]
|
||||
);
|
||||
mockTopic.andCallFake(function (key) {
|
||||
return key === 'mutation' && mockMutationTopic;
|
||||
});
|
||||
mockThrottle.andReturn(mockThrottledFn);
|
||||
mockObjectPromise.then.andReturn(mockChainedPromise);
|
||||
|
||||
provider = new GenericSearchProvider(mockQ, mockTimeout, mockObjectService, mockWorkerService, mockRoots);
|
||||
provider = new GenericSearchProvider(
|
||||
mockQ,
|
||||
mockLog,
|
||||
mockThrottle,
|
||||
mockObjectService,
|
||||
mockWorkerService,
|
||||
mockTopic,
|
||||
mockRoots
|
||||
);
|
||||
});
|
||||
|
||||
it("indexes tree on initialization", function () {
|
||||
var i;
|
||||
|
||||
resolveThrottledFn();
|
||||
|
||||
expect(mockObjectService.getObjects).toHaveBeenCalled();
|
||||
expect(mockObjectPromise.then).toHaveBeenCalled();
|
||||
|
||||
// Call through the root-getting part
|
||||
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
|
||||
resolveObjectPromises();
|
||||
|
||||
// Call through the children-getting part
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
// Array argument indicates multiple children
|
||||
mockCapabilityPromise.then.mostRecentCall.args[0]([]);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
// Call again, but for single child
|
||||
mockCapabilityPromise.then.mostRecentCall.args[0]({});
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
|
||||
expect(mockWorker.postMessage).toHaveBeenCalled();
|
||||
mockRoots.forEach(function (id) {
|
||||
expect(mockWorker.postMessage).toHaveBeenCalledWith({
|
||||
request: 'index',
|
||||
model: mockDomainObjects[id].getModel(),
|
||||
id: id
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("when indexing, listens for composition changes", function () {
|
||||
var mockListener = {composition: {}};
|
||||
it("indexes members of composition", function () {
|
||||
mockDomainObjects.root1.getModel.andReturn({
|
||||
composition: ['a']
|
||||
});
|
||||
|
||||
// Call indexItems
|
||||
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
|
||||
resolveAsyncTasks();
|
||||
resolveAsyncTasks();
|
||||
|
||||
// Call through listening for changes
|
||||
expect(mockCapability.listen).toHaveBeenCalled();
|
||||
mockCapability.listen.mostRecentCall.args[0](mockListener);
|
||||
expect(mockObjectService.getObjects).toHaveBeenCalled();
|
||||
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
|
||||
expect(mockWorker.postMessage).toHaveBeenCalledWith({
|
||||
request: 'index',
|
||||
model: mockDomainObjects.a.getModel(),
|
||||
id: 'a'
|
||||
});
|
||||
});
|
||||
|
||||
it("listens for changes to mutation", function () {
|
||||
expect(mockMutationTopic.listen)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mockMutationTopic.listen.mostRecentCall
|
||||
.args[0](mockDomainObjects.a);
|
||||
|
||||
resolveAsyncTasks();
|
||||
|
||||
expect(mockWorker.postMessage).toHaveBeenCalledWith({
|
||||
request: 'index',
|
||||
model: mockDomainObjects.a.getModel(),
|
||||
id: mockDomainObjects.a.getId()
|
||||
});
|
||||
});
|
||||
|
||||
it("sends search queries to the worker", function () {
|
||||
@ -188,6 +266,28 @@ define(
|
||||
expect(mockDeferred.resolve).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("warns when objects are unavailable", function () {
|
||||
resolveAsyncTasks();
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
mockChainedPromise.then.mostRecentCall.args[0](
|
||||
mockObjectPromise.then.mostRecentCall.args[1]()
|
||||
);
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throttles the loading of objects to index", function () {
|
||||
expect(mockObjectService.getObjects).not.toHaveBeenCalled();
|
||||
resolveThrottledFn();
|
||||
expect(mockObjectService.getObjects).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("logs when all objects have been processed", function () {
|
||||
expect(mockLog.info).not.toHaveBeenCalled();
|
||||
resolveAsyncTasks();
|
||||
resolveThrottledFn();
|
||||
expect(mockLog.info).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue
Block a user