[Telemetry] Add JSDoc

Add JSDoc and in-line comments to scripts in the
platform/telemetry bundle, for WTD-575.
This commit is contained in:
Victor Woeltjen 2014-11-28 20:59:41 -08:00
parent f8a0544435
commit 55c2d15cdc
4 changed files with 190 additions and 10 deletions

View File

@ -9,17 +9,20 @@ define(
"use strict"; "use strict";
/** /**
* A telemetry aggregator makes many telemetry providers
* appear as one.
* *
* @constructor * @constructor
*/ */
function TelemetryAggregator($q, telemetryProviders) { function TelemetryAggregator($q, telemetryProviders) {
// Merge the results from many providers into one
// result object.
function mergeResults(results) { function mergeResults(results) {
var merged = {}; var merged = {};
results.forEach(function (result) { results.forEach(function (result) {
Object.keys(result).forEach(function (k) { Object.keys(result).forEach(function (k) {
// Otherwise, just take the result
merged[k] = result[k]; merged[k] = result[k];
}); });
}); });
@ -27,6 +30,8 @@ define(
return merged; return merged;
} }
// Request telemetry from all providers; once they've
// responded, merge the results into one result object.
function requestTelemetry(requests) { function requestTelemetry(requests) {
return $q.all(telemetryProviders.map(function (provider) { return $q.all(telemetryProviders.map(function (provider) {
return provider.requestTelemetry(requests); return provider.requestTelemetry(requests);
@ -34,6 +39,14 @@ define(
} }
return { 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 requestTelemetry: requestTelemetry
}; };
} }

View File

@ -9,6 +9,9 @@ define(
"use strict"; "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 * @constructor
*/ */
@ -23,6 +26,8 @@ define(
telemetryService = telemetryService =
$q.when($injector.get("telemetryService")); $q.when($injector.get("telemetryService"));
} catch (e) { } catch (e) {
// $injector should throw is telemetryService
// is unavailable or unsatisfiable.
$log.warn("Telemetry service unavailable"); $log.warn("Telemetry service unavailable");
telemetryService = $q.reject(e); telemetryService = $q.reject(e);
} }
@ -30,21 +35,30 @@ define(
return telemetryService; 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) { function buildRequest(request) {
// Start with any "telemetry" field in type; use that as a
// basis for the request.
var type = domainObject.getCapability("type"), var type = domainObject.getCapability("type"),
typeRequest = (type && type.getDefinition().telemetry) || {}, typeRequest = (type && type.getDefinition().telemetry) || {},
modelTelemetry = domainObject.getModel().telemetry, modelTelemetry = domainObject.getModel().telemetry,
fullRequest = Object.create(typeRequest); fullRequest = Object.create(typeRequest);
// Add properties from the telemetry field of this
// specific domain object.
Object.keys(modelTelemetry).forEach(function (k) { Object.keys(modelTelemetry).forEach(function (k) {
fullRequest[k] = modelTelemetry[k]; fullRequest[k] = modelTelemetry[k];
}); });
// Add properties from this specific requestData call.
Object.keys(request).forEach(function (k) { Object.keys(request).forEach(function (k) {
fullRequest[k] = request[k]; fullRequest[k] = request[k];
}); });
// Include domain object ID, at minimum // Ensure an ID and key are present
if (!fullRequest.id) { if (!fullRequest.id) {
fullRequest.id = domainObject.getId(); fullRequest.id = domainObject.getId();
} }
@ -55,37 +69,63 @@ define(
return fullRequest; return fullRequest;
} }
// Issue a request for telemetry data
function requestTelemetry(request) { function requestTelemetry(request) {
// Bring in any defaults from the object model // Bring in any defaults from the object model
var fullRequest = buildRequest(request || {}), var fullRequest = buildRequest(request || {}),
source = fullRequest.source, source = fullRequest.source,
key = fullRequest.key; key = fullRequest.key;
// Pull out the relevant field from the larger,
// structured response.
function getRelevantResponse(response) { function getRelevantResponse(response) {
return ((response || {})[source] || {})[key] || {}; return ((response || {})[source] || {})[key] || {};
} }
// Issue a request to the service
function requestTelemetryFromService(telemetryService) { function requestTelemetryFromService(telemetryService) {
return telemetryService.requestTelemetry([fullRequest]); return telemetryService.requestTelemetry([fullRequest]);
} }
// If a telemetryService is not available,
// getTelemetryService() should reject, and this should
// bubble through subsequent then calls.
return getTelemetryService() return getTelemetryService()
.then(requestTelemetryFromService) .then(requestTelemetryFromService)
.then(getRelevantResponse); .then(getRelevantResponse);
} }
return { 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, requestData: requestTelemetry,
/**
* Get metadata about this domain object's associated
* telemetry.
*/
getMetadata: function () { getMetadata: function () {
// metadata just looks like a request,
// so use buildRequest to bring in both
// type-level and object-level telemetry
// properties
return buildRequest({}); return buildRequest({});
} }
}; };
} }
/**
* The telemetry capability is applicable when a
* domain object model has a "telemetry" field.
*/
TelemetryCapability.appliesTo = function (model) { TelemetryCapability.appliesTo = function (model) {
return (model && return (model &&
model.telemetry && model.telemetry) ? true : false;
model.telemetry.source) ? true : false;
}; };
return TelemetryCapability; return TelemetryCapability;

View File

@ -17,19 +17,44 @@ define(
*/ */
function TelemetryController($scope, $q, $timeout, $log) { function TelemetryController($scope, $q, $timeout, $log) {
// Private to maintain in this scope
var self = { var self = {
// IDs of domain objects with telemetry
ids: [], ids: [],
// Containers for latest responses (id->response)
// Used internally; see buildResponseContainer
// for format
response: {}, response: {},
// Request fields (id->requests)
request: {}, request: {},
// Number of outstanding requests
pending: 0, pending: 0,
// Array of object metadatas, for easy retrieval
metadatas: [], metadatas: [],
// Interval at which to poll for new data
interval: 1000, interval: 1000,
// Flag tracking whether or not a request
// is in progress
refreshing: false, refreshing: false,
// Used to track whether a new telemetryUpdate
// is being issued.
broadcasting: false broadcasting: false
}; };
// Broadcast that a telemetryUpdate has occurred.
function doBroadcast() { 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) { if (!self.broadcasting) {
self.broadcasting = true; self.broadcasting = true;
$timeout(function () { $timeout(function () {
@ -39,11 +64,14 @@ define(
} }
} }
// Issue a request for new telemetry for one of the
// objects being tracked by this controller
function requestTelemetryForId(id, trackPending) { function requestTelemetryForId(id, trackPending) {
var responseObject = self.response[id], var responseObject = self.response[id],
domainObject = responseObject.domainObject, domainObject = responseObject.domainObject,
telemetry = domainObject.getCapability('telemetry'); telemetry = domainObject.getCapability('telemetry');
// Callback for when data comes back
function storeData(data) { function storeData(data) {
self.pending -= trackPending ? 1 : 0; self.pending -= trackPending ? 1 : 0;
responseObject.data = data; responseObject.data = data;
@ -52,30 +80,50 @@ define(
self.pending += trackPending ? 1 : 0; self.pending += trackPending ? 1 : 0;
// Shouldn't happen, but isn't fatal,
// so warn.
if (!telemetry) { if (!telemetry) {
$log.warn([ $log.warn([
"Expected telemetry capability for ", "Expected telemetry capability for ",
id, id,
" but found none. Cannot request data." " but found none. Cannot request data."
].join("")); ].join(""));
// Request won't happen, so don't
// mark it as pending.
self.pending -= trackPending ? 1 : 0;
return; return;
} }
// Issue the request using the object's telemetry capability
return $q.when(telemetry.requestData(self.request)) return $q.when(telemetry.requestData(self.request))
.then(storeData); .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) { function requestTelemetry(trackPending) {
return $q.all(self.ids.map(function (id) { return $q.all(self.ids.map(function (id) {
return requestTelemetryForId(id, trackPending); 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) { function promiseRelevantDomainObjects(domainObject) {
// If object has been cleared, there are no relevant
// telemetry-providing domain objects.
if (!domainObject) { if (!domainObject) {
return $q.when([]); return $q.when([]);
} }
// Otherwise, try delegation first, and attach the
// object itself if it has a telemetry capability.
return $q.when(domainObject.useCapability( return $q.when(domainObject.useCapability(
"delegation", "delegation",
"telemetry" "telemetry"
@ -87,6 +135,9 @@ define(
}); });
} }
// Build the response containers that are used internally
// by this controller to track latest responses, etc, for
// a given domain object.
function buildResponseContainer(domainObject) { function buildResponseContainer(domainObject) {
var telemetry = domainObject && var telemetry = domainObject &&
domainObject.getCapability("telemetry"), domainObject.getCapability("telemetry"),
@ -103,12 +154,17 @@ define(
data: {} data: {}
}; };
} else { } else {
// Shouldn't happen, as we've checked for
// telemetry capabilities previously, but
// be defensive.
$log.warn([ $log.warn([
"Expected telemetry capability for ", "Expected telemetry capability for ",
domainObject.getId(), domainObject.getId(),
" but none was found." " but none was found."
].join("")); ].join(""));
// Create an empty container so subsequent
// behavior won't hit an exception.
self.response[domainObject.getId()] = { self.response[domainObject.getId()] = {
name: domainObject.getModel().name, name: domainObject.getModel().name,
domainObject: domainObject, domainObject: domainObject,
@ -119,11 +175,21 @@ define(
} }
} }
// Build response containers (as above) for all
// domain objects, and update some controller-internal
// state to support subsequent calls.
function buildResponseContainers(domainObjects) { function buildResponseContainers(domainObjects) {
// Build the containers
domainObjects.forEach(buildResponseContainer); 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) { self.ids = domainObjects.map(function (obj) {
return obj.getId(); return obj.getId();
}); });
// Keep a reference to all applicable metadata
// to return from getMetadata
self.metadatas = self.ids.map(function (id) { self.metadatas = self.ids.map(function (id) {
return self.response[id].metadata; return self.response[id].metadata;
}); });
@ -135,11 +201,16 @@ define(
} }
} }
// 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) { function getTelemetryObjects(domainObject) {
promiseRelevantDomainObjects(domainObject) promiseRelevantDomainObjects(domainObject)
.then(buildResponseContainers); .then(buildResponseContainers);
} }
// Handle a polling refresh interval
function startTimeout() { function startTimeout() {
if (!self.refreshing && self.interval !== undefined) { if (!self.refreshing && self.interval !== undefined) {
self.refreshing = true; self.refreshing = true;
@ -154,19 +225,58 @@ define(
} }
} }
// Watch for a represented domain object
$scope.$watch("domainObject", getTelemetryObjects); $scope.$watch("domainObject", getTelemetryObjects);
startTimeout(); // Begin refreshing
// Begin polling for data changes
startTimeout();
return { 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 () { getMetadata: function () {
return self.metadatas; 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 () { getTelemetryObjects: function () {
return self.ids.map(function (id) { return self.ids.map(function (id) {
return self.response[id].domainObject; 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) { getResponse: function getResponse(arg) {
var id = arg && (typeof arg === 'string' ? var id = arg && (typeof arg === 'string' ?
arg : arg.getId()); arg : arg.getId());
@ -177,13 +287,33 @@ define(
return (self.ids || []).map(getResponse); 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 () { isRequestPending: function () {
return self.pending > 0; 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) { requestData: function (request) {
self.request = request || {}; self.request = request || {};
return requestTelemetry(true); 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) { setRefreshInterval: function (durationMillis) {
self.interval = durationMillis; self.interval = durationMillis;
startTimeout(); startTimeout();

View File

@ -65,9 +65,6 @@ define(
expect(TelemetryCapability.appliesTo({ expect(TelemetryCapability.appliesTo({
telemetry: { source: "testSource" } telemetry: { source: "testSource" }
})).toBeTruthy(); })).toBeTruthy();
expect(TelemetryCapability.appliesTo({
telemetry: { xsource: "testSource" }
})).toBeFalsy();
expect(TelemetryCapability.appliesTo({ expect(TelemetryCapability.appliesTo({
xtelemetry: { source: "testSource" } xtelemetry: { source: "testSource" }
})).toBeFalsy(); })).toBeFalsy();