mirror of
https://github.com/nasa/openmct.git
synced 2025-02-20 17:33:23 +00:00
Merge remote-tracking branch 'origin/wtd575' into open-master
Conflicts: bundles.json
This commit is contained in:
commit
505b6ee241
@ -6,7 +6,7 @@
|
||||
"platform/commonUI/edit",
|
||||
"platform/commonUI/dialog",
|
||||
"platform/commonUI/general",
|
||||
"platform/forms",
|
||||
|
||||
"platform/telemetry",
|
||||
|
||||
"example/persistence"
|
||||
]
|
2
platform/telemetry/README.md
Normal file
2
platform/telemetry/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
This bundle is responsible for introducing a reusable infrastructure
|
||||
and set of APIs for using time-series data in Open MCT Web.
|
28
platform/telemetry/bundle.json
Normal file
28
platform/telemetry/bundle.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Data bundle",
|
||||
"description": "Interfaces and infrastructure for real-time and historical data.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"provides": "telemetryService",
|
||||
"type": "aggregator",
|
||||
"implementation": "TelemetryAggregator.js",
|
||||
"depends": [ "$q" ]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TelemetryController",
|
||||
"implementation": "TelemetryController.js",
|
||||
"depends": [ "$scope", "$q", "$timeout", "$log" ]
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "telemetry",
|
||||
"implementation": "TelemetryCapability.js",
|
||||
"depends": [ "$injector", "$q", "$log" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
56
platform/telemetry/src/TelemetryAggregator.js
Normal file
56
platform/telemetry/src/TelemetryAggregator.js
Normal file
@ -0,0 +1,56 @@
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Module defining TelemetryProvider. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A telemetry aggregator makes many telemetry providers
|
||||
* appear as one.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryAggregator($q, telemetryProviders) {
|
||||
|
||||
// Merge the results from many providers into one
|
||||
// result object.
|
||||
function mergeResults(results) {
|
||||
var merged = {};
|
||||
|
||||
results.forEach(function (result) {
|
||||
Object.keys(result).forEach(function (k) {
|
||||
merged[k] = result[k];
|
||||
});
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
// Request telemetry from all providers; once they've
|
||||
// responded, merge the results into one result object.
|
||||
function requestTelemetry(requests) {
|
||||
return $q.all(telemetryProviders.map(function (provider) {
|
||||
return provider.requestTelemetry(requests);
|
||||
})).then(mergeResults);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Request telemetry data.
|
||||
* @param {TelemetryRequest[]} requests and array of
|
||||
* requests to be handled
|
||||
* @returns {Promise} a promise for telemetry data
|
||||
* which may (or may not, depending on
|
||||
* availability) satisfy the requests
|
||||
*/
|
||||
requestTelemetry: requestTelemetry
|
||||
};
|
||||
}
|
||||
|
||||
return TelemetryAggregator;
|
||||
}
|
||||
);
|
133
platform/telemetry/src/TelemetryCapability.js
Normal file
133
platform/telemetry/src/TelemetryCapability.js
Normal file
@ -0,0 +1,133 @@
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Module defining TelemetryCapability. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A telemetry capability provides a means of requesting telemetry
|
||||
* for a specific object, and for unwrapping the response (to get
|
||||
* at the specific data which is appropriate to the domain object.)
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryCapability($injector, $q, $log, domainObject) {
|
||||
var telemetryService;
|
||||
|
||||
// We could depend on telemetryService directly, but
|
||||
// there isn't a platform implementation of this;
|
||||
function getTelemetryService() {
|
||||
if (!telemetryService) {
|
||||
try {
|
||||
telemetryService =
|
||||
$q.when($injector.get("telemetryService"));
|
||||
} catch (e) {
|
||||
// $injector should throw is telemetryService
|
||||
// is unavailable or unsatisfiable.
|
||||
$log.warn("Telemetry service unavailable");
|
||||
telemetryService = $q.reject(e);
|
||||
}
|
||||
}
|
||||
return telemetryService;
|
||||
}
|
||||
|
||||
// Build a request object. This takes the request that was
|
||||
// passed to the capability, and adds source, id, and key
|
||||
// fields associated with the object (from its type definition
|
||||
// and/or its model)
|
||||
function buildRequest(request) {
|
||||
// Start with any "telemetry" field in type; use that as a
|
||||
// basis for the request.
|
||||
var type = domainObject.getCapability("type"),
|
||||
typeRequest = (type && type.getDefinition().telemetry) || {},
|
||||
modelTelemetry = domainObject.getModel().telemetry,
|
||||
fullRequest = Object.create(typeRequest);
|
||||
|
||||
// Add properties from the telemetry field of this
|
||||
// specific domain object.
|
||||
Object.keys(modelTelemetry).forEach(function (k) {
|
||||
fullRequest[k] = modelTelemetry[k];
|
||||
});
|
||||
|
||||
// Add properties from this specific requestData call.
|
||||
Object.keys(request).forEach(function (k) {
|
||||
fullRequest[k] = request[k];
|
||||
});
|
||||
|
||||
// Ensure an ID and key are present
|
||||
if (!fullRequest.id) {
|
||||
fullRequest.id = domainObject.getId();
|
||||
}
|
||||
if (!fullRequest.key) {
|
||||
fullRequest.key = domainObject.getId();
|
||||
}
|
||||
|
||||
return fullRequest;
|
||||
}
|
||||
|
||||
// Issue a request for telemetry data
|
||||
function requestTelemetry(request) {
|
||||
// Bring in any defaults from the object model
|
||||
var fullRequest = buildRequest(request || {}),
|
||||
source = fullRequest.source,
|
||||
key = fullRequest.key;
|
||||
|
||||
// Pull out the relevant field from the larger,
|
||||
// structured response.
|
||||
function getRelevantResponse(response) {
|
||||
return ((response || {})[source] || {})[key] || {};
|
||||
}
|
||||
|
||||
// Issue a request to the service
|
||||
function requestTelemetryFromService(telemetryService) {
|
||||
return telemetryService.requestTelemetry([fullRequest]);
|
||||
}
|
||||
|
||||
// If a telemetryService is not available,
|
||||
// getTelemetryService() should reject, and this should
|
||||
// bubble through subsequent then calls.
|
||||
return getTelemetryService()
|
||||
.then(requestTelemetryFromService)
|
||||
.then(getRelevantResponse);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Request telemetry data for this specific domain object.
|
||||
* @param {TelemetryRequest} [request] parameters for this
|
||||
* specific request
|
||||
* @returns {Promise} a promise for the resulting telemetry
|
||||
* object
|
||||
*/
|
||||
requestData: requestTelemetry,
|
||||
|
||||
/**
|
||||
* Get metadata about this domain object's associated
|
||||
* telemetry.
|
||||
*/
|
||||
getMetadata: function () {
|
||||
// metadata just looks like a request,
|
||||
// so use buildRequest to bring in both
|
||||
// type-level and object-level telemetry
|
||||
// properties
|
||||
return buildRequest({});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The telemetry capability is applicable when a
|
||||
* domain object model has a "telemetry" field.
|
||||
*/
|
||||
TelemetryCapability.appliesTo = function (model) {
|
||||
return (model &&
|
||||
model.telemetry) ? true : false;
|
||||
};
|
||||
|
||||
return TelemetryCapability;
|
||||
}
|
||||
);
|
326
platform/telemetry/src/TelemetryController.js
Normal file
326
platform/telemetry/src/TelemetryController.js
Normal file
@ -0,0 +1,326 @@
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Module defining TelemetryController. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Serves as a reusable controller for views (or parts of views)
|
||||
* which need to issue requests for telemetry data and use the
|
||||
* results
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryController($scope, $q, $timeout, $log) {
|
||||
|
||||
// Private to maintain in this scope
|
||||
var self = {
|
||||
// IDs of domain objects with telemetry
|
||||
ids: [],
|
||||
|
||||
// Containers for latest responses (id->response)
|
||||
// Used internally; see buildResponseContainer
|
||||
// for format
|
||||
response: {},
|
||||
|
||||
// Request fields (id->requests)
|
||||
request: {},
|
||||
|
||||
// Number of outstanding requests
|
||||
pending: 0,
|
||||
|
||||
// Array of object metadatas, for easy retrieval
|
||||
metadatas: [],
|
||||
|
||||
// Interval at which to poll for new data
|
||||
interval: 1000,
|
||||
|
||||
// Flag tracking whether or not a request
|
||||
// is in progress
|
||||
refreshing: false,
|
||||
|
||||
// Used to track whether a new telemetryUpdate
|
||||
// is being issued.
|
||||
broadcasting: false
|
||||
};
|
||||
|
||||
// Broadcast that a telemetryUpdate has occurred.
|
||||
function doBroadcast() {
|
||||
// This may get called multiple times from
|
||||
// multiple objects, so set a flag to suppress
|
||||
// multiple simultaneous events from being
|
||||
// broadcast, then issue the actual broadcast
|
||||
// later (via $timeout)
|
||||
if (!self.broadcasting) {
|
||||
self.broadcasting = true;
|
||||
$timeout(function () {
|
||||
self.broadcasting = false;
|
||||
$scope.$broadcast("telemetryUpdate");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Issue a request for new telemetry for one of the
|
||||
// objects being tracked by this controller
|
||||
function requestTelemetryForId(id, trackPending) {
|
||||
var responseObject = self.response[id],
|
||||
domainObject = responseObject.domainObject,
|
||||
telemetry = domainObject.getCapability('telemetry');
|
||||
|
||||
// Callback for when data comes back
|
||||
function storeData(data) {
|
||||
self.pending -= trackPending ? 1 : 0;
|
||||
responseObject.data = data;
|
||||
doBroadcast();
|
||||
}
|
||||
|
||||
self.pending += trackPending ? 1 : 0;
|
||||
|
||||
// Shouldn't happen, but isn't fatal,
|
||||
// so warn.
|
||||
if (!telemetry) {
|
||||
$log.warn([
|
||||
"Expected telemetry capability for ",
|
||||
id,
|
||||
" but found none. Cannot request data."
|
||||
].join(""));
|
||||
|
||||
// Request won't happen, so don't
|
||||
// mark it as pending.
|
||||
self.pending -= trackPending ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue the request using the object's telemetry capability
|
||||
return $q.when(telemetry.requestData(self.request))
|
||||
.then(storeData);
|
||||
}
|
||||
|
||||
// Request telemetry for all objects tracked by this
|
||||
// controller. A flag is passed to indicate whether the
|
||||
// pending counter should be incremented (this will
|
||||
// cause isRequestPending() to change, which we only
|
||||
// want to happen for requests which have originated
|
||||
// outside of this controller's polling action.)
|
||||
function requestTelemetry(trackPending) {
|
||||
return $q.all(self.ids.map(function (id) {
|
||||
return requestTelemetryForId(id, trackPending);
|
||||
}));
|
||||
}
|
||||
|
||||
// Look up domain objects which have telemetry capabilities.
|
||||
// This will either be the object in view, or object that
|
||||
// this object delegates its telemetry capability to.
|
||||
function promiseRelevantDomainObjects(domainObject) {
|
||||
// If object has been cleared, there are no relevant
|
||||
// telemetry-providing domain objects.
|
||||
if (!domainObject) {
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
// Otherwise, try delegation first, and attach the
|
||||
// object itself if it has a telemetry capability.
|
||||
return $q.when(domainObject.useCapability(
|
||||
"delegation",
|
||||
"telemetry"
|
||||
)).then(function (result) {
|
||||
var head = domainObject.hasCapability("telemetry") ?
|
||||
[ domainObject ] : [],
|
||||
tail = result || [];
|
||||
return head.concat(tail);
|
||||
});
|
||||
}
|
||||
|
||||
// Build the response containers that are used internally
|
||||
// by this controller to track latest responses, etc, for
|
||||
// a given domain object.
|
||||
function buildResponseContainer(domainObject) {
|
||||
var telemetry = domainObject &&
|
||||
domainObject.getCapability("telemetry"),
|
||||
metadata;
|
||||
|
||||
if (telemetry) {
|
||||
metadata = telemetry.getMetadata();
|
||||
|
||||
self.response[domainObject.getId()] = {
|
||||
name: domainObject.getModel().name,
|
||||
domainObject: domainObject,
|
||||
metadata: metadata,
|
||||
pending: 0,
|
||||
data: {}
|
||||
};
|
||||
} else {
|
||||
// Shouldn't happen, as we've checked for
|
||||
// telemetry capabilities previously, but
|
||||
// be defensive.
|
||||
$log.warn([
|
||||
"Expected telemetry capability for ",
|
||||
domainObject.getId(),
|
||||
" but none was found."
|
||||
].join(""));
|
||||
|
||||
// Create an empty container so subsequent
|
||||
// behavior won't hit an exception.
|
||||
self.response[domainObject.getId()] = {
|
||||
name: domainObject.getModel().name,
|
||||
domainObject: domainObject,
|
||||
metadata: {},
|
||||
pending: 0,
|
||||
data: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Build response containers (as above) for all
|
||||
// domain objects, and update some controller-internal
|
||||
// state to support subsequent calls.
|
||||
function buildResponseContainers(domainObjects) {
|
||||
// Build the containers
|
||||
domainObjects.forEach(buildResponseContainer);
|
||||
|
||||
// Maintain a list of relevant ids, to convert
|
||||
// back from dictionary-like container objects to arrays.
|
||||
self.ids = domainObjects.map(function (obj) {
|
||||
return obj.getId();
|
||||
});
|
||||
|
||||
// Keep a reference to all applicable metadata
|
||||
// to return from getMetadata
|
||||
self.metadatas = self.ids.map(function (id) {
|
||||
return self.response[id].metadata;
|
||||
});
|
||||
|
||||
// Issue a request for the new objects, if we
|
||||
// know what our request looks like
|
||||
if (self.request) {
|
||||
requestTelemetry(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Get relevant telemetry-providing domain objects
|
||||
// for the domain object which is represented in this
|
||||
// scope. This will be the domain object itself, or
|
||||
// its telemetry delegates, or both.
|
||||
function getTelemetryObjects(domainObject) {
|
||||
promiseRelevantDomainObjects(domainObject)
|
||||
.then(buildResponseContainers);
|
||||
}
|
||||
|
||||
// Handle a polling refresh interval
|
||||
function startTimeout() {
|
||||
if (!self.refreshing && self.interval !== undefined) {
|
||||
self.refreshing = true;
|
||||
$timeout(function () {
|
||||
if (self.request) {
|
||||
requestTelemetry(false);
|
||||
}
|
||||
|
||||
self.refreshing = false;
|
||||
startTimeout();
|
||||
}, self.interval);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for a represented domain object
|
||||
$scope.$watch("domainObject", getTelemetryObjects);
|
||||
|
||||
// Begin polling for data changes
|
||||
startTimeout();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get all telemetry metadata associated with
|
||||
* telemetry-providing domain objects managed by
|
||||
* this controller.
|
||||
*
|
||||
* This will ordered in the
|
||||
* same manner as `getTelemetryObjects()` or
|
||||
* `getResponse()`; that is, the metadata at a
|
||||
* given index will correspond to the telemetry-providing
|
||||
* domain object at the same index.
|
||||
* @returns {Array} an array of metadata objects
|
||||
*/
|
||||
getMetadata: function () {
|
||||
return self.metadatas;
|
||||
},
|
||||
/**
|
||||
* Get all telemetry-providing domain objects managed by
|
||||
* this controller.
|
||||
*
|
||||
* This will ordered in the
|
||||
* same manner as `getMetadata()` or
|
||||
* `getResponse()`; that is, the metadata at a
|
||||
* given index will correspond to the telemetry-providing
|
||||
* domain object at the same index.
|
||||
* @returns {DomainObject[]} an array of metadata objects
|
||||
*/
|
||||
getTelemetryObjects: function () {
|
||||
return self.ids.map(function (id) {
|
||||
return self.response[id].domainObject;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get the latest telemetry response for a specific
|
||||
* domain object (if an argument is given) or for all
|
||||
* objects managed by this controller (if no argument
|
||||
* is supplied.)
|
||||
*
|
||||
* In the first form, this returns a single object; in
|
||||
* the second form, it returns an array ordered in
|
||||
* same manner as `getMetadata()` or
|
||||
* `getTelemetryObjects()`; that is, the telemetry
|
||||
* response at agiven index will correspond to the
|
||||
* telemetry-providing domain object at the same index.
|
||||
* @returns {Array} an array of responses
|
||||
*/
|
||||
getResponse: function getResponse(arg) {
|
||||
var id = arg && (typeof arg === 'string' ?
|
||||
arg : arg.getId());
|
||||
|
||||
if (id) {
|
||||
return (self.response[id] || {}).data;
|
||||
}
|
||||
|
||||
return (self.ids || []).map(getResponse);
|
||||
},
|
||||
/**
|
||||
* Check if the latest request (not counting
|
||||
* requests from TelemtryController's own polling)
|
||||
* is still outstanding. Users of the TelemetryController
|
||||
* may use this method as a condition upon which to
|
||||
* show user feedback, such as a wait spinner.
|
||||
*
|
||||
* @returns {boolean} true if the request is still outstanding
|
||||
*/
|
||||
isRequestPending: function () {
|
||||
return self.pending > 0;
|
||||
},
|
||||
/**
|
||||
* Issue a new data request. This will change the
|
||||
* request parameters that are passed along to all
|
||||
* telemetry capabilities managed by this controller.
|
||||
*/
|
||||
requestData: function (request) {
|
||||
self.request = request || {};
|
||||
return requestTelemetry(true);
|
||||
},
|
||||
/**
|
||||
* Change the interval at which this controller will
|
||||
* perform its polling activity.
|
||||
* @param {number} durationMillis the interval at
|
||||
* which to poll, in milliseconds
|
||||
*/
|
||||
setRefreshInterval: function (durationMillis) {
|
||||
self.interval = durationMillis;
|
||||
startTimeout();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return TelemetryController;
|
||||
}
|
||||
);
|
80
platform/telemetry/test/TelemetryAggregatorSpec.js
Normal file
80
platform/telemetry/test/TelemetryAggregatorSpec.js
Normal file
@ -0,0 +1,80 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TelemetryAggregator"],
|
||||
function (TelemetryAggregator) {
|
||||
"use strict";
|
||||
|
||||
describe("The telemetry aggregator", function () {
|
||||
var mockQ,
|
||||
mockProviders,
|
||||
aggregator;
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mockProvider(key, index) {
|
||||
var provider = jasmine.createSpyObj(
|
||||
"provider" + index,
|
||||
[ "requestTelemetry" ]
|
||||
);
|
||||
provider.requestTelemetry.andReturn({ someKey: key });
|
||||
return provider;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj("$q", [ "all" ]);
|
||||
mockQ.all.andReturn(mockPromise([]));
|
||||
|
||||
mockProviders = [ "a", "b", "c" ].map(mockProvider);
|
||||
|
||||
aggregator = new TelemetryAggregator(mockQ, mockProviders);
|
||||
});
|
||||
|
||||
it("passes requests to aggregated providers", function () {
|
||||
var requests = [
|
||||
{ someKey: "some value" },
|
||||
{ someKey: "some other value" }
|
||||
];
|
||||
|
||||
aggregator.requestTelemetry(requests);
|
||||
|
||||
mockProviders.forEach(function (mockProvider) {
|
||||
expect(mockProvider.requestTelemetry)
|
||||
.toHaveBeenCalledWith(requests);
|
||||
});
|
||||
});
|
||||
|
||||
it("merges results from all providers", function () {
|
||||
var capture = jasmine.createSpy("capture");
|
||||
|
||||
mockQ.all.andReturn(mockPromise([
|
||||
{ someKey: "some value" },
|
||||
{ someOtherKey: "some other value" }
|
||||
]));
|
||||
|
||||
aggregator.requestTelemetry().then(capture);
|
||||
|
||||
// Verify that aggregator results were run through
|
||||
// $q.all
|
||||
expect(mockQ.all).toHaveBeenCalledWith([
|
||||
{ someKey: 'a' },
|
||||
{ someKey: 'b' },
|
||||
{ someKey: 'c' }
|
||||
]);
|
||||
|
||||
expect(capture).toHaveBeenCalledWith({
|
||||
someKey: "some value",
|
||||
someOtherKey: "some other value"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
129
platform/telemetry/test/TelemetryCapabilitySpec.js
Normal file
129
platform/telemetry/test/TelemetryCapabilitySpec.js
Normal file
@ -0,0 +1,129 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TelemetryCapability"],
|
||||
function (TelemetryCapability) {
|
||||
"use strict";
|
||||
|
||||
describe("The telemetry capability", function () {
|
||||
var mockInjector,
|
||||
mockQ,
|
||||
mockLog,
|
||||
mockDomainObject,
|
||||
mockTelemetryService,
|
||||
mockReject,
|
||||
telemetry;
|
||||
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
|
||||
mockQ = jasmine.createSpyObj("$q", ["when", "reject"]);
|
||||
mockLog = jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getCapability", "getModel" ]
|
||||
);
|
||||
mockTelemetryService = jasmine.createSpyObj(
|
||||
"telemetryService",
|
||||
[ "requestTelemetry" ]
|
||||
);
|
||||
mockReject = jasmine.createSpyObj("reject", ["then"]);
|
||||
|
||||
mockInjector.get.andReturn(mockTelemetryService);
|
||||
|
||||
mockQ.when.andCallFake(mockPromise);
|
||||
mockQ.reject.andReturn(mockReject);
|
||||
|
||||
mockDomainObject.getId.andReturn("testId");
|
||||
mockDomainObject.getModel.andReturn({
|
||||
telemetry: {
|
||||
source: "testSource",
|
||||
key: "testKey"
|
||||
}
|
||||
});
|
||||
|
||||
// Bubble up...
|
||||
mockReject.then.andReturn(mockReject);
|
||||
|
||||
telemetry = new TelemetryCapability(
|
||||
mockInjector,
|
||||
mockQ,
|
||||
mockLog,
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
|
||||
it("applies only to objects with telemetry sources", function () {
|
||||
expect(TelemetryCapability.appliesTo({
|
||||
telemetry: { source: "testSource" }
|
||||
})).toBeTruthy();
|
||||
expect(TelemetryCapability.appliesTo({
|
||||
xtelemetry: { source: "testSource" }
|
||||
})).toBeFalsy();
|
||||
expect(TelemetryCapability.appliesTo({})).toBeFalsy();
|
||||
expect(TelemetryCapability.appliesTo()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("gets a telemetry service from the injector", function () {
|
||||
telemetry.requestData();
|
||||
expect(mockInjector.get)
|
||||
.toHaveBeenCalledWith("telemetryService");
|
||||
});
|
||||
|
||||
it("applies request arguments", function () {
|
||||
telemetry.requestData({ start: 42 });
|
||||
expect(mockTelemetryService.requestTelemetry)
|
||||
.toHaveBeenCalledWith([{
|
||||
id: "testId", // from domain object
|
||||
source: "testSource", // from model
|
||||
key: "testKey", // from model
|
||||
start: 42 // from argument
|
||||
}]);
|
||||
|
||||
});
|
||||
|
||||
it("provides telemetry metadata", function () {
|
||||
expect(telemetry.getMetadata()).toEqual({
|
||||
id: "testId", // from domain object
|
||||
source: "testSource",
|
||||
key: "testKey"
|
||||
});
|
||||
});
|
||||
|
||||
it("uses domain object as a key if needed", function () {
|
||||
// Don't include key in telemetry
|
||||
mockDomainObject.getModel.andReturn({
|
||||
telemetry: { source: "testSource" }
|
||||
});
|
||||
|
||||
// Should have used the domain object's ID
|
||||
expect(telemetry.getMetadata()).toEqual({
|
||||
id: "testId", // from domain object
|
||||
source: "testSource", // from model
|
||||
key: "testId" // from domain object
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("warns if no telemetry service can be injected", function () {
|
||||
mockInjector.get.andCallFake(function () { throw ""; });
|
||||
|
||||
// Verify precondition
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
|
||||
telemetry.requestData();
|
||||
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
193
platform/telemetry/test/TelemetryControllerSpec.js
Normal file
193
platform/telemetry/test/TelemetryControllerSpec.js
Normal file
@ -0,0 +1,193 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TelemetryController"],
|
||||
function (TelemetryController) {
|
||||
"use strict";
|
||||
|
||||
describe("The telemetry controller", function () {
|
||||
var mockScope,
|
||||
mockQ,
|
||||
mockTimeout,
|
||||
mockLog,
|
||||
mockDomainObject,
|
||||
mockTelemetry,
|
||||
controller;
|
||||
|
||||
function mockPromise(value) {
|
||||
return (value && value.then) ? value : {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$on", "$broadcast", "$watch" ]
|
||||
);
|
||||
mockQ = jasmine.createSpyObj("$q", [ "all", "when" ]);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockLog = jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[
|
||||
"getId",
|
||||
"getCapability",
|
||||
"getModel",
|
||||
"hasCapability",
|
||||
"useCapability"
|
||||
]
|
||||
);
|
||||
|
||||
mockTelemetry = jasmine.createSpyObj(
|
||||
"telemetry",
|
||||
[ "requestData", "getMetadata" ]
|
||||
);
|
||||
|
||||
mockQ.when.andCallFake(mockPromise);
|
||||
mockQ.all.andReturn(mockPromise([mockDomainObject]));
|
||||
|
||||
mockDomainObject.getId.andReturn("testId");
|
||||
mockDomainObject.getModel.andReturn({ name: "TEST" });
|
||||
mockDomainObject.useCapability.andReturn([]);
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andReturn(mockTelemetry);
|
||||
|
||||
mockTelemetry.getMetadata.andReturn({
|
||||
source: "testSource",
|
||||
key: "testKey"
|
||||
});
|
||||
mockTelemetry.requestData.andReturn(mockPromise({
|
||||
telemetryKey: "some value"
|
||||
}));
|
||||
|
||||
controller = new TelemetryController(
|
||||
mockScope,
|
||||
mockQ,
|
||||
mockTimeout,
|
||||
mockLog
|
||||
);
|
||||
});
|
||||
|
||||
it("watches the domain object in scope", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"domainObject",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("starts a refresh interval", function () {
|
||||
expect(mockTimeout).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Number)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes refresh interval on request", function () {
|
||||
controller.setRefreshInterval(42);
|
||||
|
||||
// Tick the clock; should issue a new request, with
|
||||
// the new interval
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
|
||||
expect(mockTimeout).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
42
|
||||
);
|
||||
});
|
||||
|
||||
it("requests data from domain objects", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(mockTelemetry.requestData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("logs a warning if no telemetry capability exists", function () {
|
||||
mockDomainObject.getCapability.andReturn(undefined);
|
||||
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("provides telemetry metadata", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(controller.getMetadata()).toEqual([
|
||||
{ source: "testSource", key: "testKey" }
|
||||
]);
|
||||
});
|
||||
|
||||
it("provides telemetry-possessing domain objects", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(controller.getTelemetryObjects())
|
||||
.toEqual([mockDomainObject]);
|
||||
});
|
||||
|
||||
it("provides telemetry data", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(controller.getResponse())
|
||||
.toEqual([{telemetryKey: "some value"}]);
|
||||
});
|
||||
|
||||
it("provides telemetry data per-id", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
expect(controller.getResponse("testId"))
|
||||
.toEqual({telemetryKey: "some value"});
|
||||
});
|
||||
|
||||
it("provides a check for pending requests", function () {
|
||||
expect(controller.isRequestPending()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("allows a request to be specified", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
controller.requestData({ someKey: "some request" });
|
||||
|
||||
expect(mockTelemetry.requestData).toHaveBeenCalledWith({
|
||||
someKey: "some request"
|
||||
});
|
||||
});
|
||||
|
||||
it("allows an object to be removed from scope", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](undefined);
|
||||
|
||||
expect(controller.getTelemetryObjects())
|
||||
.toEqual([]);
|
||||
});
|
||||
|
||||
it("broadcasts when telemetry is available", function () {
|
||||
// Push into the scope...
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
controller.requestData({ someKey: "some request" });
|
||||
|
||||
// Verify precondition
|
||||
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||
|
||||
// Call the broadcast timeout
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
|
||||
// Should have broadcast a telemetryUpdate
|
||||
expect(mockScope.$broadcast)
|
||||
.toHaveBeenCalledWith("telemetryUpdate");
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
5
platform/telemetry/test/suite.json
Normal file
5
platform/telemetry/test/suite.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
"TelemetryAggregator",
|
||||
"TelemetryCapability",
|
||||
"TelemetryController"
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user