Merge branch 'open1515' into open115
This commit is contained in:
9
platform/features/conductor/README.md
Normal file
9
platform/features/conductor/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Provides the time conductor, a control which appears at the
|
||||
bottom of the screen allowing telemetry start and end times
|
||||
to be modified.
|
||||
|
||||
Note that the term "time controller" is generally preferred
|
||||
outside of the code base (e.g. in UI documents, issues, etc.);
|
||||
the term "time conductor" is being used in code to avoid
|
||||
confusion with "controllers" in the Model-View-Controller
|
||||
sense.
|
||||
25
platform/features/conductor/bundle.json
Normal file
25
platform/features/conductor/bundle.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extensions": {
|
||||
"representers": [
|
||||
{
|
||||
"implementation": "ConductorRepresenter.js",
|
||||
"depends": [ "conductorService", "$compile", "views[]" ]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"provides": "telemetryService",
|
||||
"implementation": "ConductorTelemetryDecorator.js",
|
||||
"depends": [ "conductorService" ]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "conductorService",
|
||||
"implementation": "ConductorService.js",
|
||||
"depends": [ "now" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
167
platform/features/conductor/src/ConductorRepresenter.js
Normal file
167
platform/features/conductor/src/ConductorRepresenter.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/*****************************************************************************
|
||||
* 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";
|
||||
|
||||
var CONDUCTOR_HEIGHT = "100px",
|
||||
TEMPLATE = [
|
||||
'<div style=',
|
||||
'"position: absolute; bottom: 0; width: 100%; ',
|
||||
'overflow: hidden; ',
|
||||
'height: ' + CONDUCTOR_HEIGHT + '">',
|
||||
"<mct-include key=\"'time-controller'\" ng-model='conductor'>",
|
||||
"</mct-include>",
|
||||
'</div>'
|
||||
].join(''),
|
||||
GLOBAL_SHOWING = false;
|
||||
|
||||
/**
|
||||
* The ConductorRepresenter attaches the universal time conductor
|
||||
* to views.
|
||||
*
|
||||
* @implements {Representer}
|
||||
* @constructor
|
||||
* @memberof platform/features/conductor
|
||||
* @param {platform/features/conductor.ConductorService} conductorService
|
||||
* service which provides the active time conductor
|
||||
* @param $compile Angular's $compile
|
||||
* @param {ViewDefinition[]} views all defined views
|
||||
* @param {Scope} the scope of the representation
|
||||
* @param element the jqLite-wrapped representation element
|
||||
*/
|
||||
function ConductorRepresenter(conductorService, $compile, views, scope, element) {
|
||||
this.scope = scope;
|
||||
this.conductorService = conductorService;
|
||||
this.element = element;
|
||||
this.views = views;
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
// Combine start/end times into a single object
|
||||
function bounds(start, end) {
|
||||
return { start: start, end: end };
|
||||
}
|
||||
|
||||
// Update the time conductor from the scope
|
||||
function wireScope(conductor, conductorScope, repScope) {
|
||||
function updateConductorOuter() {
|
||||
conductor.queryStart(conductorScope.conductor.outer.start);
|
||||
conductor.queryEnd(conductorScope.conductor.outer.end);
|
||||
repScope.$broadcast(
|
||||
'telemetry:query:bounds',
|
||||
bounds(conductor.queryStart(), conductor.queryEnd())
|
||||
);
|
||||
}
|
||||
|
||||
function updateConductorInner() {
|
||||
conductor.displayStart(conductorScope.conductor.inner.start);
|
||||
conductor.displayEnd(conductorScope.conductor.inner.end);
|
||||
repScope.$broadcast(
|
||||
'telemetry:display:bounds',
|
||||
bounds(conductor.displayStart(), conductor.displayEnd())
|
||||
);
|
||||
}
|
||||
|
||||
conductorScope.conductor = {
|
||||
outer: bounds(conductor.queryStart(), conductor.queryEnd()),
|
||||
inner: bounds(conductor.displayStart(), conductor.displayEnd())
|
||||
};
|
||||
|
||||
conductorScope
|
||||
.$watch('conductor.outer.start', updateConductorOuter);
|
||||
conductorScope
|
||||
.$watch('conductor.outer.end', updateConductorOuter);
|
||||
conductorScope
|
||||
.$watch('conductor.inner.start', updateConductorInner);
|
||||
conductorScope
|
||||
.$watch('conductor.inner.end', updateConductorInner);
|
||||
|
||||
repScope.$on('telemetry:view', updateConductorInner);
|
||||
}
|
||||
|
||||
ConductorRepresenter.prototype.conductorScope = function (s) {
|
||||
return (this.cScope = arguments.length > 0 ?
|
||||
s : this.cScope);
|
||||
};
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
ConductorRepresenter.prototype.represent = function represent(representation, representedObject) {
|
||||
this.destroy();
|
||||
|
||||
if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) {
|
||||
// Track original states
|
||||
this.originalHeight = this.element.css('height');
|
||||
this.hadAbs = this.element.hasClass('abs');
|
||||
|
||||
// Create a new scope for the conductor
|
||||
this.conductorScope(this.scope.$new());
|
||||
wireScope(
|
||||
this.conductorService.getConductor(),
|
||||
this.conductorScope(),
|
||||
this.scope
|
||||
);
|
||||
this.conductorElement =
|
||||
this.$compile(TEMPLATE)(this.conductorScope());
|
||||
this.element.after(this.conductorElement[0]);
|
||||
this.element.addClass('abs');
|
||||
this.element.css('bottom', CONDUCTOR_HEIGHT);
|
||||
GLOBAL_SHOWING = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Respond to the destruction of the current representation.
|
||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||
// We may not have decided to show in the first place,
|
||||
// so circumvent any unnecessary cleanup
|
||||
if (!this.conductorElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the original size of the mct-representation
|
||||
if (!this.hadAbs) {
|
||||
this.element.removeClass('abs');
|
||||
}
|
||||
this.element.css('height', this.originalHeight);
|
||||
|
||||
// ...and remove the conductor
|
||||
if (this.conductorElement) {
|
||||
this.conductorElement.remove();
|
||||
this.conductorElement = undefined;
|
||||
}
|
||||
|
||||
// Finally, destroy its scope
|
||||
if (this.conductorScope()) {
|
||||
this.conductorScope().$destroy();
|
||||
this.conductorScope(undefined);
|
||||
}
|
||||
|
||||
GLOBAL_SHOWING = false;
|
||||
};
|
||||
|
||||
return ConductorRepresenter;
|
||||
}
|
||||
);
|
||||
|
||||
61
platform/features/conductor/src/ConductorService.js
Normal file
61
platform/features/conductor/src/ConductorService.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./TimeConductor'],
|
||||
function (TimeConductor) {
|
||||
'use strict';
|
||||
|
||||
var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24,
|
||||
SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4;
|
||||
|
||||
/**
|
||||
* Provides a single global instance of the time conductor, which
|
||||
* controls both query ranges and displayed ranges for telemetry
|
||||
* data.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/conductor
|
||||
* @param {Function} now a function which returns the current time
|
||||
* as a UNIX timestamp, in milliseconds
|
||||
*/
|
||||
function ConductorService(now) {
|
||||
var initialEnd =
|
||||
Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS;
|
||||
|
||||
this.conductor =
|
||||
new TimeConductor(initialEnd - ONE_DAY_IN_MS, initialEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global instance of the time conductor.
|
||||
* @returns {platform/features/conductor.TimeConductor} the
|
||||
* time conductor
|
||||
*/
|
||||
ConductorService.prototype.getConductor = function () {
|
||||
return this.conductor;
|
||||
};
|
||||
|
||||
return ConductorService;
|
||||
}
|
||||
);
|
||||
108
platform/features/conductor/src/ConductorTelemetryDecorator.js
Normal file
108
platform/features/conductor/src/ConductorTelemetryDecorator.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./ConductorTelemetrySeries'],
|
||||
function (ConductorTelemetrySeries) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Decorates the `telemetryService` such that requests are
|
||||
* mediated by the time conductor.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/conductor
|
||||
* @implements {TelemetryService}
|
||||
* @param {platform/features/conductor.ConductorService} conductorServe
|
||||
* the service which exposes the global time conductor
|
||||
* @param {TelemetryService} telemetryService the decorated service
|
||||
*/
|
||||
function ConductorTelemetryDecorator(conductorService, telemetryService) {
|
||||
this.conductorService = conductorService;
|
||||
this.telemetryService = telemetryService;
|
||||
}
|
||||
|
||||
// Strip out any realtime data series that is outside of the conductor's
|
||||
// bounds.
|
||||
ConductorTelemetryDecorator.prototype.pruneNonDisplayable = function (packaged) {
|
||||
var conductor = this.conductorService.getConductor(),
|
||||
repackaged = {};
|
||||
|
||||
function filterSource(packagedBySource) {
|
||||
var repackagedBySource = {};
|
||||
|
||||
Object.keys(packagedBySource).forEach(function (k) {
|
||||
repackagedBySource[k] = new ConductorTelemetrySeries(
|
||||
packagedBySource[k],
|
||||
conductor
|
||||
);
|
||||
});
|
||||
|
||||
return repackagedBySource;
|
||||
}
|
||||
|
||||
Object.keys(packaged).forEach(function (source) {
|
||||
repackaged[source] = filterSource(packaged[source]);
|
||||
});
|
||||
|
||||
return repackaged;
|
||||
};
|
||||
|
||||
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
|
||||
var conductor = this.conductorService.getConductor(),
|
||||
start = conductor.displayStart(),
|
||||
end = conductor.displayEnd();
|
||||
|
||||
function amendRequest(request) {
|
||||
request = request || {};
|
||||
request.start = start;
|
||||
request.end = end;
|
||||
return request;
|
||||
}
|
||||
|
||||
return (requests || []).map(amendRequest);
|
||||
};
|
||||
|
||||
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
|
||||
var self = this;
|
||||
return this.telemetryService
|
||||
.requestTelemetry(this.amendRequests(requests))
|
||||
.then(function (packaged) {
|
||||
return self.pruneNonDisplayable(packaged);
|
||||
});
|
||||
};
|
||||
|
||||
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
|
||||
var self = this;
|
||||
|
||||
function internalCallback(packagedSeries) {
|
||||
return callback(self.pruneNonDisplayable(packagedSeries));
|
||||
}
|
||||
|
||||
return this.telemetryService
|
||||
.subscribe(internalCallback, this.amendRequests(requests));
|
||||
};
|
||||
|
||||
return ConductorTelemetryDecorator;
|
||||
}
|
||||
);
|
||||
71
platform/features/conductor/src/ConductorTelemetrySeries.js
Normal file
71
platform/features/conductor/src/ConductorTelemetrySeries.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/*****************************************************************************
|
||||
* 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';
|
||||
|
||||
|
||||
/**
|
||||
* Bound a series of telemetry such that it only includes
|
||||
* points from within the time conductor's displayable window.
|
||||
*
|
||||
* @param {TelemetrySeries} series the telemetry series
|
||||
* @param {platform/features/conductor.TimeConductor} the
|
||||
* time conductor instance which bounds this series
|
||||
* @constructor
|
||||
* @implements {TelemetrySeries}
|
||||
*/
|
||||
function ConductorTelemetrySeries(series, conductor) {
|
||||
var max = series.getPointCount() - 1;
|
||||
|
||||
function binSearch(min, max, value) {
|
||||
var mid = Math.floor((min + max) / 2);
|
||||
|
||||
return min > max ? min :
|
||||
series.getDomainValue(mid) < value ?
|
||||
binSearch(mid + 1, max, value) :
|
||||
binSearch(min, mid - 1, value);
|
||||
}
|
||||
|
||||
this.startIndex = binSearch(0, max, conductor.displayStart());
|
||||
this.endIndex = binSearch(0, max, conductor.displayEnd());
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
ConductorTelemetrySeries.prototype.getPointCount = function () {
|
||||
return Math.max(0, this.endIndex - this.startIndex);
|
||||
};
|
||||
|
||||
ConductorTelemetrySeries.prototype.getDomainValue = function (i, d) {
|
||||
return this.series.getDomainValue(i + this.startIndex, d);
|
||||
};
|
||||
|
||||
ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) {
|
||||
return this.series.getRangeValue(i + this.startIndex, r);
|
||||
};
|
||||
|
||||
return ConductorTelemetrySeries;
|
||||
}
|
||||
);
|
||||
99
platform/features/conductor/src/TimeConductor.js
Normal file
99
platform/features/conductor/src/TimeConductor.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/*****************************************************************************
|
||||
* 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*/
|
||||
|
||||
/**
|
||||
* The time conductor bundle adds a global control to the bottom of the
|
||||
* outermost viewing area. This controls both the range for time-based
|
||||
* queries and for time-based displays.
|
||||
*
|
||||
* @namespace platform/features/conductor
|
||||
*/
|
||||
define(
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Tracks the current state of the time conductor.
|
||||
*
|
||||
* @memberof platform/features/conductor
|
||||
* @constructor
|
||||
* @param {number} start the initial start time
|
||||
* @param {number} end the initial end time
|
||||
*/
|
||||
function TimeConductor(start, end) {
|
||||
this.inner = { start: start, end: end };
|
||||
this.outer = { start: start, end: end };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the start time for queries.
|
||||
* @param {number} [value] the start time to set
|
||||
* @returns {number} the start time
|
||||
*/
|
||||
TimeConductor.prototype.queryStart = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.outer.start = value;
|
||||
}
|
||||
return this.outer.start;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the end time for queries.
|
||||
* @param {number} [value] the end time to set
|
||||
* @returns {number} the end time
|
||||
*/
|
||||
TimeConductor.prototype.queryEnd = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.outer.end = value;
|
||||
}
|
||||
return this.outer.end;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the start time for displays.
|
||||
* @param {number} [value] the start time to set
|
||||
* @returns {number} the start time
|
||||
*/
|
||||
TimeConductor.prototype.displayStart = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.inner.start = value;
|
||||
}
|
||||
return this.inner.start;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the end time for displays.
|
||||
* @param {number} [value] the end time to set
|
||||
* @returns {number} the end time
|
||||
*/
|
||||
TimeConductor.prototype.displayEnd = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.inner.end = value;
|
||||
}
|
||||
return this.inner.end;
|
||||
};
|
||||
|
||||
return TimeConductor;
|
||||
}
|
||||
);
|
||||
167
platform/features/conductor/test/ConductorRepresenterSpec.js
Normal file
167
platform/features/conductor/test/ConductorRepresenterSpec.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/*****************************************************************************
|
||||
* 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,describe,it,expect,beforeEach,waitsFor,afterEach,jasmine*/
|
||||
|
||||
/**
|
||||
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||
*/
|
||||
define(
|
||||
["../src/ConductorRepresenter"],
|
||||
function (ConductorRepresenter) {
|
||||
"use strict";
|
||||
|
||||
var SCOPE_METHODS = [
|
||||
'$on',
|
||||
'$watch',
|
||||
'$broadcast',
|
||||
'$emit',
|
||||
'$new',
|
||||
'$destroy'
|
||||
],
|
||||
ELEMENT_METHODS = [
|
||||
'hasClass',
|
||||
'addClass',
|
||||
'removeClass',
|
||||
'css',
|
||||
'after',
|
||||
'remove'
|
||||
];
|
||||
|
||||
describe("ConductorRepresenter", function () {
|
||||
var mockConductorService,
|
||||
mockCompile,
|
||||
testViews,
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockConductor,
|
||||
mockCompiledTemplate,
|
||||
mockNewScope,
|
||||
mockNewElement,
|
||||
representer;
|
||||
|
||||
function fireWatch(scope, watch, value) {
|
||||
scope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === watch) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockConductorService = jasmine.createSpyObj(
|
||||
'conductorService',
|
||||
['getConductor']
|
||||
);
|
||||
mockCompile = jasmine.createSpy('$compile');
|
||||
testViews = [ { someKey: "some value" } ];
|
||||
mockScope = jasmine.createSpyObj('scope', SCOPE_METHODS);
|
||||
mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS);
|
||||
mockConductor = jasmine.createSpyObj(
|
||||
'conductor',
|
||||
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||
);
|
||||
mockCompiledTemplate = jasmine.createSpy('template');
|
||||
mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS);
|
||||
mockNewElement = jasmine.createSpyObj('newElement', ELEMENT_METHODS);
|
||||
mockNewElement[0] = mockNewElement;
|
||||
|
||||
mockConductorService.getConductor.andReturn(mockConductor);
|
||||
mockCompile.andReturn(mockCompiledTemplate);
|
||||
mockCompiledTemplate.andReturn(mockNewElement);
|
||||
mockScope.$new.andReturn(mockNewScope);
|
||||
|
||||
representer = new ConductorRepresenter(
|
||||
mockConductorService,
|
||||
mockCompile,
|
||||
testViews,
|
||||
mockScope,
|
||||
mockElement
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
representer.destroy();
|
||||
});
|
||||
|
||||
it("adds a conductor to views", function () {
|
||||
representer.represent(testViews[0], {});
|
||||
expect(mockElement.after).toHaveBeenCalledWith(mockNewElement);
|
||||
});
|
||||
|
||||
it("adds nothing to non-view representations", function () {
|
||||
representer.represent({ someKey: "something else" }, {});
|
||||
expect(mockElement.after).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes the conductor when destroyed", function () {
|
||||
representer.represent(testViews[0], {});
|
||||
expect(mockNewElement.remove).not.toHaveBeenCalled();
|
||||
representer.destroy();
|
||||
expect(mockNewElement.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("destroys any new scope created", function () {
|
||||
representer.represent(testViews[0], {});
|
||||
representer.destroy();
|
||||
expect(mockNewScope.$destroy.calls.length)
|
||||
.toEqual(mockScope.$new.calls.length);
|
||||
});
|
||||
|
||||
it("exposes conductor state in scope", function () {
|
||||
mockConductor.queryStart.andReturn(42);
|
||||
mockConductor.queryEnd.andReturn(12321);
|
||||
mockConductor.displayStart.andReturn(1977);
|
||||
mockConductor.displayEnd.andReturn(1984);
|
||||
representer.represent(testViews[0], {});
|
||||
|
||||
expect(mockNewScope.conductor).toEqual({
|
||||
inner: { start: 1977, end: 1984 },
|
||||
outer: { start: 42, end: 12321 }
|
||||
});
|
||||
});
|
||||
|
||||
it("updates conductor state from scope", function () {
|
||||
var testState = {
|
||||
inner: { start: 42, end: 1984 },
|
||||
outer: { start: -1977, end: 12321 }
|
||||
};
|
||||
|
||||
representer.represent(testViews[0], {});
|
||||
|
||||
mockNewScope.conductor = testState;
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.inner.start', testState.inner.start);
|
||||
expect(mockConductor.displayStart).toHaveBeenCalledWith(42);
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testState.inner.end);
|
||||
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.outer.start', testState.outer.start);
|
||||
expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977);
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.outer.end', testState.outer.end);
|
||||
expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
58
platform/features/conductor/test/ConductorServiceSpec.js
Normal file
58
platform/features/conductor/test/ConductorServiceSpec.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
/**
|
||||
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||
*/
|
||||
define(
|
||||
["../src/ConductorService"],
|
||||
function (ConductorService) {
|
||||
"use strict";
|
||||
|
||||
var TEST_NOW = 1020304050;
|
||||
|
||||
describe("ConductorService", function () {
|
||||
var mockNow,
|
||||
conductorService;
|
||||
|
||||
beforeEach(function () {
|
||||
mockNow = jasmine.createSpy('now');
|
||||
mockNow.andReturn(TEST_NOW);
|
||||
conductorService = new ConductorService(mockNow);
|
||||
});
|
||||
|
||||
it("initializes a time conductor around the current time", function () {
|
||||
var conductor = conductorService.getConductor();
|
||||
expect(conductor.queryStart() <= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.queryEnd() >= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.queryEnd() > conductor.queryStart())
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("provides a single shared time conductor instance", function () {
|
||||
expect(conductorService.getConductor())
|
||||
.toBe(conductorService.getConductor());
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,137 @@
|
||||
/*****************************************************************************
|
||||
* 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
|
||||
define(
|
||||
["../src/ConductorTelemetryDecorator"],
|
||||
function (ConductorTelemetryDecorator) {
|
||||
"use strict";
|
||||
|
||||
describe("ConductorTelemetryDecorator", function () {
|
||||
var mockTelemetryService,
|
||||
mockConductorService,
|
||||
mockConductor,
|
||||
mockPromise,
|
||||
mockSeries,
|
||||
decorator;
|
||||
|
||||
function seriesIsInWindow(series) {
|
||||
var i, v, inWindow = true;
|
||||
for (i = 0; i < series.getPointCount(); i += 1) {
|
||||
v = series.getDomainValue(i);
|
||||
inWindow = inWindow && (v >= mockConductor.displayStart());
|
||||
inWindow = inWindow && (v <= mockConductor.displayEnd());
|
||||
}
|
||||
return inWindow;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockTelemetryService = jasmine.createSpyObj(
|
||||
'telemetryService',
|
||||
[ 'requestTelemetry', 'subscribe' ]
|
||||
);
|
||||
mockConductorService = jasmine.createSpyObj(
|
||||
'conductorService',
|
||||
['getConductor']
|
||||
);
|
||||
mockConductor = jasmine.createSpyObj(
|
||||
'conductor',
|
||||
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||
);
|
||||
mockPromise = jasmine.createSpyObj(
|
||||
'promise',
|
||||
['then']
|
||||
);
|
||||
mockSeries = jasmine.createSpyObj(
|
||||
'series',
|
||||
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
||||
);
|
||||
|
||||
mockTelemetryService.requestTelemetry.andReturn(mockPromise);
|
||||
mockConductorService.getConductor.andReturn(mockConductor);
|
||||
|
||||
// Prepare test series; make sure it has a broad range of
|
||||
// domain values, with at least some in the query-able range
|
||||
mockSeries.getPointCount.andReturn(1000);
|
||||
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||
var j = i - 500;
|
||||
return j * j * j;
|
||||
});
|
||||
|
||||
mockConductor.queryStart.andReturn(-12321);
|
||||
mockConductor.queryEnd.andReturn(-12321);
|
||||
mockConductor.displayStart.andReturn(42);
|
||||
mockConductor.displayEnd.andReturn(1977);
|
||||
|
||||
decorator = new ConductorTelemetryDecorator(
|
||||
mockConductorService,
|
||||
mockTelemetryService
|
||||
);
|
||||
});
|
||||
|
||||
it("adds display start/end times to historical requests", function () {
|
||||
decorator.requestTelemetry([{ someKey: "some value" }]);
|
||||
expect(mockTelemetryService.requestTelemetry)
|
||||
.toHaveBeenCalledWith([{
|
||||
someKey: "some value",
|
||||
start: mockConductor.displayStart(),
|
||||
end: mockConductor.displayEnd()
|
||||
}]);
|
||||
});
|
||||
|
||||
it("adds display start/end times to subscription requests", function () {
|
||||
var mockCallback = jasmine.createSpy('callback');
|
||||
decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
|
||||
expect(mockTelemetryService.subscribe)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function), [{
|
||||
someKey: "some value",
|
||||
start: mockConductor.displayStart(),
|
||||
end: mockConductor.displayEnd()
|
||||
}]);
|
||||
});
|
||||
|
||||
it("prunes historical values to the displayable range", function () {
|
||||
var packagedTelemetry;
|
||||
decorator.requestTelemetry([{ source: "abc", key: "xyz" }]);
|
||||
packagedTelemetry = mockPromise.then.mostRecentCall.args[0]({
|
||||
"abc": { "xyz": mockSeries }
|
||||
});
|
||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("prunes subscribed values to the displayable range", function () {
|
||||
var mockCallback = jasmine.createSpy('callback'),
|
||||
packagedTelemetry;
|
||||
decorator.subscribe(mockCallback, [{ source: "abc", key: "xyz" }]);
|
||||
mockTelemetryService.subscribe.mostRecentCall.args[0]({
|
||||
"abc": { "xyz": mockSeries }
|
||||
});
|
||||
packagedTelemetry = mockCallback.mostRecentCall.args[0];
|
||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,86 @@
|
||||
/*****************************************************************************
|
||||
* 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ConductorTelemetrySeries"],
|
||||
function (ConductorTelemetrySeries) {
|
||||
"use strict";
|
||||
|
||||
describe("ConductorTelemetrySeries", function () {
|
||||
var mockSeries,
|
||||
mockConductor,
|
||||
testArray,
|
||||
series;
|
||||
|
||||
beforeEach(function () {
|
||||
testArray = [ -10, 0, 42, 1977, 12321 ];
|
||||
|
||||
mockSeries = jasmine.createSpyObj(
|
||||
'series',
|
||||
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
||||
);
|
||||
mockConductor = jasmine.createSpyObj(
|
||||
'conductor',
|
||||
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||
);
|
||||
|
||||
mockSeries.getPointCount.andCallFake(function () {
|
||||
return testArray.length;
|
||||
});
|
||||
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||
return testArray[i];
|
||||
});
|
||||
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||
return testArray[i] * 2;
|
||||
});
|
||||
|
||||
mockConductor.displayStart.andReturn(0);
|
||||
mockConductor.displayEnd.andReturn(2000);
|
||||
|
||||
series = new ConductorTelemetrySeries(
|
||||
mockSeries,
|
||||
mockConductor
|
||||
);
|
||||
});
|
||||
|
||||
it("reduces the apparent size of a series", function () {
|
||||
expect(series.getPointCount()).toEqual(3);
|
||||
});
|
||||
|
||||
it("maps domain value indexes to the displayable range", function () {
|
||||
[0, 1, 2].forEach(function (i) {
|
||||
expect(series.getDomainValue(i))
|
||||
.toEqual(mockSeries.getDomainValue(i + 1));
|
||||
});
|
||||
});
|
||||
|
||||
it("maps range value indexes to the displayable range", function () {
|
||||
[0, 1, 2].forEach(function (i) {
|
||||
expect(series.getRangeValue(i))
|
||||
.toEqual(mockSeries.getRangeValue(i + 1));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
63
platform/features/conductor/test/TimeConductorSpec.js
Normal file
63
platform/features/conductor/test/TimeConductorSpec.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
/**
|
||||
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||
*/
|
||||
define(
|
||||
["../src/TimeConductor"],
|
||||
function (TimeConductor) {
|
||||
"use strict";
|
||||
|
||||
describe("TimeConductor", function () {
|
||||
var testStart,
|
||||
testEnd,
|
||||
conductor;
|
||||
|
||||
beforeEach(function () {
|
||||
testStart = 42;
|
||||
testEnd = 12321;
|
||||
conductor = new TimeConductor(testStart, testEnd);
|
||||
});
|
||||
|
||||
it("provides accessors for query/display start/end times", function () {
|
||||
expect(conductor.queryStart()).toEqual(testStart);
|
||||
expect(conductor.queryEnd()).toEqual(testEnd);
|
||||
expect(conductor.displayStart()).toEqual(testStart);
|
||||
expect(conductor.displayEnd()).toEqual(testEnd);
|
||||
});
|
||||
|
||||
it("provides setters for query/display start/end times", function () {
|
||||
expect(conductor.queryStart(1)).toEqual(1);
|
||||
expect(conductor.queryEnd(2)).toEqual(2);
|
||||
expect(conductor.displayStart(3)).toEqual(3);
|
||||
expect(conductor.displayEnd(4)).toEqual(4);
|
||||
expect(conductor.queryStart()).toEqual(1);
|
||||
expect(conductor.queryEnd()).toEqual(2);
|
||||
expect(conductor.displayStart()).toEqual(3);
|
||||
expect(conductor.displayEnd()).toEqual(4);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
7
platform/features/conductor/test/suite.json
Normal file
7
platform/features/conductor/test/suite.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"ConductorRepresenter",
|
||||
"ConductorService",
|
||||
"ConductorTelemetryDecorator",
|
||||
"ConductorTelemetrySeries",
|
||||
"TimeConductor"
|
||||
]
|
||||
@@ -167,8 +167,9 @@
|
||||
"$scope",
|
||||
"$q",
|
||||
"dialogService",
|
||||
"telemetrySubscriber",
|
||||
"telemetryFormatter"
|
||||
"telemetryHandler",
|
||||
"telemetryFormatter",
|
||||
"throttle"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -38,12 +38,13 @@ define(
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function FixedController($scope, $q, dialogService, telemetrySubscriber, telemetryFormatter) {
|
||||
function FixedController($scope, $q, dialogService, telemetryHandler, telemetryFormatter, throttle) {
|
||||
var self = this,
|
||||
subscription,
|
||||
handle,
|
||||
names = {}, // Cache names by ID
|
||||
values = {}, // Cache values by ID
|
||||
elementProxiesById = {};
|
||||
elementProxiesById = {},
|
||||
maxDomainValue = Number.POSITIVE_INFINITY;
|
||||
|
||||
// Convert from element x/y/width/height to an
|
||||
// appropriate ng-style argument, to position elements.
|
||||
@@ -81,25 +82,50 @@ define(
|
||||
return element.handles().map(generateDragHandle);
|
||||
}
|
||||
|
||||
// Update the displayed value for this object
|
||||
function updateValue(telemetryObject) {
|
||||
var id = telemetryObject && telemetryObject.getId(),
|
||||
// Update the value displayed in elements of this telemetry object
|
||||
function setDisplayedValue(telemetryObject, value, alarm) {
|
||||
var id = telemetryObject.getId();
|
||||
(elementProxiesById[id] || []).forEach(function (element) {
|
||||
names[id] = telemetryObject.getModel().name;
|
||||
values[id] = telemetryFormatter.formatRangeValue(value);
|
||||
element.name = names[id];
|
||||
element.value = values[id];
|
||||
element.cssClass = alarm && alarm.cssClass;
|
||||
});
|
||||
}
|
||||
|
||||
// Update the displayed value for this object, from a specific
|
||||
// telemetry series
|
||||
function updateValueFromSeries(telemetryObject, telemetrySeries) {
|
||||
var index = telemetrySeries.getPointCount() - 1,
|
||||
limit = telemetryObject &&
|
||||
telemetryObject.getCapability('limit'),
|
||||
datum = telemetryObject &&
|
||||
subscription.getDatum(telemetryObject),
|
||||
alarm = limit && datum && limit.evaluate(datum);
|
||||
datum = telemetryObject && handle.getDatum(
|
||||
telemetryObject,
|
||||
index
|
||||
);
|
||||
|
||||
if (id) {
|
||||
(elementProxiesById[id] || []).forEach(function (element) {
|
||||
names[id] = telemetryObject.getModel().name;
|
||||
values[id] = telemetryFormatter.formatRangeValue(
|
||||
subscription.getRangeValue(telemetryObject)
|
||||
);
|
||||
element.name = names[id];
|
||||
element.value = values[id];
|
||||
element.cssClass = alarm && alarm.cssClass;
|
||||
});
|
||||
setDisplayedValue(
|
||||
telemetryObject,
|
||||
telemetrySeries.getRangeValue(index),
|
||||
limit && datum && limit.evaluate(datum)
|
||||
);
|
||||
}
|
||||
|
||||
// Update the displayed value for this object
|
||||
function updateValue(telemetryObject) {
|
||||
var limit = telemetryObject &&
|
||||
telemetryObject.getCapability('limit'),
|
||||
datum = telemetryObject &&
|
||||
handle.getDatum(telemetryObject);
|
||||
|
||||
if (telemetryObject &&
|
||||
(handle.getDomainValue(telemetryObject) < maxDomainValue)) {
|
||||
setDisplayedValue(
|
||||
telemetryObject,
|
||||
handle.getRangeValue(telemetryObject),
|
||||
limit && datum && limit.evaluate(datum)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +141,8 @@ define(
|
||||
|
||||
// Update telemetry values based on new data available
|
||||
function updateValues() {
|
||||
if (subscription) {
|
||||
subscription.getTelemetryObjects().forEach(updateValue);
|
||||
if (handle) {
|
||||
handle.getTelemetryObjects().forEach(updateValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,22 +204,29 @@ define(
|
||||
|
||||
// Free up subscription to telemetry
|
||||
function releaseSubscription() {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
handle = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to telemetry updates for this domain object
|
||||
function subscribe(domainObject) {
|
||||
// Release existing subscription (if any)
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
}
|
||||
|
||||
// Make a new subscription
|
||||
subscription = domainObject &&
|
||||
telemetrySubscriber.subscribe(domainObject, updateValues);
|
||||
handle = domainObject && telemetryHandler.handle(
|
||||
domainObject,
|
||||
updateValues
|
||||
);
|
||||
// Request an initial historical telemetry value
|
||||
handle.request(
|
||||
{ size: 1 }, // Only need a single data point
|
||||
updateValueFromSeries
|
||||
);
|
||||
}
|
||||
|
||||
// Handle changes in the object's composition
|
||||
@@ -204,6 +237,17 @@ define(
|
||||
subscribe($scope.domainObject);
|
||||
}
|
||||
|
||||
// Trigger a new query for telemetry data
|
||||
function updateDisplayBounds(event, bounds) {
|
||||
maxDomainValue = bounds.end;
|
||||
if (handle) {
|
||||
handle.request(
|
||||
{ size: 1 }, // Only need a single data point
|
||||
updateValueFromSeries
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add an element to this view
|
||||
function addElement(element) {
|
||||
// Ensure that configuration field is populated
|
||||
@@ -278,6 +322,9 @@ define(
|
||||
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
// Respond to external bounds changes
|
||||
$scope.$on("telemetry:display:bounds", updateDisplayBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,10 +30,10 @@ define(
|
||||
var mockScope,
|
||||
mockQ,
|
||||
mockDialogService,
|
||||
mockSubscriber,
|
||||
mockHandler,
|
||||
mockFormatter,
|
||||
mockDomainObject,
|
||||
mockSubscription,
|
||||
mockHandle,
|
||||
mockEvent,
|
||||
testGrid,
|
||||
testModel,
|
||||
@@ -78,9 +78,9 @@ define(
|
||||
'$scope',
|
||||
[ "$on", "$watch", "commit" ]
|
||||
);
|
||||
mockSubscriber = jasmine.createSpyObj(
|
||||
'telemetrySubscriber',
|
||||
[ 'subscribe' ]
|
||||
mockHandler = jasmine.createSpyObj(
|
||||
'telemetryHandler',
|
||||
[ 'handle' ]
|
||||
);
|
||||
mockQ = jasmine.createSpyObj('$q', ['when']);
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
@@ -95,9 +95,16 @@ define(
|
||||
'domainObject',
|
||||
[ 'getId', 'getModel', 'getCapability' ]
|
||||
);
|
||||
mockSubscription = jasmine.createSpyObj(
|
||||
mockHandle = jasmine.createSpyObj(
|
||||
'subscription',
|
||||
[ 'unsubscribe', 'getTelemetryObjects', 'getRangeValue', 'getDatum' ]
|
||||
[
|
||||
'unsubscribe',
|
||||
'getDomainValue',
|
||||
'getTelemetryObjects',
|
||||
'getRangeValue',
|
||||
'getDatum',
|
||||
'request'
|
||||
]
|
||||
);
|
||||
mockEvent = jasmine.createSpyObj(
|
||||
'event',
|
||||
@@ -116,13 +123,14 @@ define(
|
||||
{ type: "fixed.telemetry", id: 'c', x: 1, y: 1 }
|
||||
]};
|
||||
|
||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||
mockSubscription.getTelemetryObjects.andReturn(
|
||||
mockHandler.handle.andReturn(mockHandle);
|
||||
mockHandle.getTelemetryObjects.andReturn(
|
||||
testModel.composition.map(makeMockDomainObject)
|
||||
);
|
||||
mockSubscription.getRangeValue.andCallFake(function (o) {
|
||||
mockHandle.getRangeValue.andCallFake(function (o) {
|
||||
return testValues[o.getId()];
|
||||
});
|
||||
mockHandle.getDomainValue.andReturn(12321);
|
||||
mockFormatter.formatRangeValue.andCallFake(function (v) {
|
||||
return "Formatted " + v;
|
||||
});
|
||||
@@ -137,7 +145,7 @@ define(
|
||||
mockScope,
|
||||
mockQ,
|
||||
mockDialogService,
|
||||
mockSubscriber,
|
||||
mockHandler,
|
||||
mockFormatter
|
||||
);
|
||||
});
|
||||
@@ -145,7 +153,7 @@ define(
|
||||
it("subscribes when a domain object is available", function () {
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||
expect(mockHandler.handle).toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
@@ -156,13 +164,13 @@ define(
|
||||
|
||||
// First pass - should simply should subscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockSubscriber.subscribe.calls.length).toEqual(1);
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(1);
|
||||
|
||||
// Object changes - should unsubscribe then resubscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockSubscriber.subscribe.calls.length).toEqual(2);
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("exposes visible elements based on configuration", function () {
|
||||
@@ -255,7 +263,7 @@ define(
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
|
||||
// Invoke the subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
|
||||
// Get elements that controller is now exposing
|
||||
elements = controller.getElements();
|
||||
@@ -333,11 +341,11 @@ define(
|
||||
// Make an object available
|
||||
findWatch('domainObject')(mockDomainObject);
|
||||
// Also verify precondition
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
findOn('$destroy')();
|
||||
// Should have unsubscribed
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exposes its grid size", function () {
|
||||
|
||||
@@ -65,6 +65,8 @@ define(
|
||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||
cachedObjects = [],
|
||||
updater,
|
||||
lastBounds,
|
||||
throttledRequery,
|
||||
handle;
|
||||
|
||||
// Populate the scope with axis information (specifically, options
|
||||
@@ -94,6 +96,17 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
// Change the displayable bounds
|
||||
function setBasePanZoom(bounds) {
|
||||
var start = bounds.start,
|
||||
end = bounds.end;
|
||||
if (updater) {
|
||||
updater.setDomainBounds(start, end);
|
||||
self.update();
|
||||
}
|
||||
lastBounds = bounds;
|
||||
}
|
||||
|
||||
// Reinstantiate the plot updater (e.g. because we have a
|
||||
// new subscription.) This will clear the plot.
|
||||
function recreateUpdater() {
|
||||
@@ -107,10 +120,15 @@ define(
|
||||
handle,
|
||||
($scope.axes[1].active || {}).key
|
||||
);
|
||||
// Keep any externally-provided bounds
|
||||
if (lastBounds) {
|
||||
setBasePanZoom(lastBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new telemetry data in this plot
|
||||
function updateValues() {
|
||||
self.pending = false;
|
||||
if (handle) {
|
||||
setupModes(handle.getTelemetryObjects());
|
||||
}
|
||||
@@ -126,6 +144,7 @@ define(
|
||||
|
||||
// Display new historical data as it becomes available
|
||||
function addHistoricalData(domainObject, series) {
|
||||
self.pending = false;
|
||||
updater.addHistorical(domainObject, series);
|
||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||
self.update();
|
||||
@@ -165,6 +184,19 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
// Respond to a display bounds change (requery for data)
|
||||
function changeDisplayBounds(event, bounds) {
|
||||
self.pending = true;
|
||||
releaseSubscription();
|
||||
throttledRequery();
|
||||
setBasePanZoom(bounds);
|
||||
}
|
||||
|
||||
// Reestablish/reissue request for telemetry
|
||||
throttledRequery = throttle(function () {
|
||||
subscribe($scope.domainObject);
|
||||
}, 250);
|
||||
|
||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||
this.updateValues = updateValues;
|
||||
|
||||
@@ -174,12 +206,19 @@ define(
|
||||
.forEach(updateSubplot);
|
||||
});
|
||||
|
||||
self.pending = true;
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
$scope.$watch('domainObject', subscribe);
|
||||
|
||||
// Respond to external bounds changes
|
||||
$scope.$on("telemetry:display:bounds", changeDisplayBounds);
|
||||
|
||||
// Unsubscribe when the plot is destroyed
|
||||
$scope.$on("$destroy", releaseSubscription);
|
||||
|
||||
// Notify any external observers that a new telemetry view is here
|
||||
$scope.$emit("telemetry:view");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,7 +314,7 @@ define(
|
||||
PlotController.prototype.isRequestPending = function () {
|
||||
// Placeholder; this should reflect request state
|
||||
// when requesting historical telemetry
|
||||
return false;
|
||||
return this.pending;
|
||||
};
|
||||
|
||||
return PlotController;
|
||||
|
||||
@@ -143,8 +143,7 @@ define(
|
||||
PlotPanZoomStackGroup.prototype.getDepth = function () {
|
||||
// All stacks are kept in sync, so look up depth
|
||||
// from the first one.
|
||||
return this.stacks.length > 0 ?
|
||||
this.stacks[0].getDepth() : 0;
|
||||
return this.stacks.length > 0 ? this.stacks[0].getDepth() : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,10 +141,10 @@ define(
|
||||
PlotUpdater.prototype.initializeDomainOffset = function (values) {
|
||||
this.domainOffset =
|
||||
((this.domainOffset === undefined) && (values.length > 0)) ?
|
||||
(values.reduce(function (a, b) {
|
||||
return (a || 0) + (b || 0);
|
||||
}, 0) / values.length) :
|
||||
this.domainOffset;
|
||||
(values.reduce(function (a, b) {
|
||||
return (a || 0) + (b || 0);
|
||||
}, 0) / values.length) :
|
||||
this.domainOffset;
|
||||
};
|
||||
|
||||
// Expand range slightly so points near edges are visible
|
||||
@@ -159,7 +159,10 @@ define(
|
||||
|
||||
// Update dimensions and origin based on extrema of plots
|
||||
PlotUpdater.prototype.updateBounds = function () {
|
||||
var bufferArray = this.bufferArray;
|
||||
var bufferArray = this.bufferArray,
|
||||
priorDomainOrigin = this.origin[0],
|
||||
priorDomainDimensions = this.dimensions[0];
|
||||
|
||||
if (bufferArray.length > 0) {
|
||||
this.domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getDomainExtrema();
|
||||
@@ -178,6 +181,18 @@ define(
|
||||
// Enforce some minimum visible area
|
||||
this.expandRange();
|
||||
|
||||
// Suppress domain changes when pinned
|
||||
if (this.hasSpecificDomainBounds) {
|
||||
this.origin[0] = priorDomainOrigin;
|
||||
this.dimensions[0] = priorDomainDimensions;
|
||||
if (this.following) {
|
||||
this.origin[0] = Math.max(
|
||||
this.domainExtrema[1] - this.dimensions[0],
|
||||
this.origin[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ...then enforce a fixed duration if needed
|
||||
if (this.fixedDuration !== undefined) {
|
||||
this.origin[0] = this.origin[0] + this.dimensions[0] -
|
||||
@@ -281,6 +296,21 @@ define(
|
||||
return this.bufferArray;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the start and end boundaries (usually time) for the
|
||||
* domain axis of this updater.
|
||||
*/
|
||||
PlotUpdater.prototype.setDomainBounds = function (start, end) {
|
||||
this.fixedDuration = end - start;
|
||||
this.origin[0] = start;
|
||||
this.dimensions[0] = this.fixedDuration;
|
||||
|
||||
// Suppress follow behavior if we have windowed in on the past
|
||||
this.hasSpecificDomainBounds = true;
|
||||
this.following =
|
||||
!this.domainExtrema || (end >= this.domainExtrema[1]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill in historical data.
|
||||
*/
|
||||
|
||||
@@ -45,11 +45,19 @@ define(
|
||||
};
|
||||
}
|
||||
|
||||
function fireEvent(name, args) {
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === name) {
|
||||
call.args[1].apply(null, args || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$watch", "$on" ]
|
||||
[ "$watch", "$on", "$emit" ]
|
||||
);
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
@@ -87,6 +95,7 @@ define(
|
||||
mockHandle.getMetadata.andReturn([{}]);
|
||||
mockHandle.getDomainValue.andReturn(123);
|
||||
mockHandle.getRangeValue.andReturn(42);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
controller = new PlotController(
|
||||
mockScope,
|
||||
@@ -212,7 +221,12 @@ define(
|
||||
});
|
||||
|
||||
it("indicates if a request is pending", function () {
|
||||
// Placeholder; need to support requesting telemetry
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(controller.isRequestPending()).toBeTruthy();
|
||||
mockHandle.request.mostRecentCall.args[1](
|
||||
mockDomainObject,
|
||||
mockSeries
|
||||
);
|
||||
expect(controller.isRequestPending()).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -233,10 +247,20 @@ define(
|
||||
// Also verify precondition
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
fireEvent("$destroy");
|
||||
// Should have unsubscribed
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requeries when displayable bounds change", function () {
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockHandle.request.calls.length).toEqual(1);
|
||||
fireEvent("telemetry:display:bounds", [
|
||||
{},
|
||||
{ start: 10, end: 100 }
|
||||
]);
|
||||
expect(mockHandle.request.calls.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ define(
|
||||
var range = this.rangeMetadata.key,
|
||||
limit = domainObject.getCapability('limit'),
|
||||
value = datum[range],
|
||||
alarm = limit.evaluate(datum, range);
|
||||
alarm = limit && limit.evaluate(datum, range);
|
||||
|
||||
return {
|
||||
cssClass: alarm && alarm.cssClass,
|
||||
|
||||
Reference in New Issue
Block a user