mirror of
https://github.com/nasa/openmct.git
synced 2025-07-01 21:01:11 +00:00
Compare commits
27 Commits
view-api-s
...
telemetry-
Author | SHA1 | Date | |
---|---|---|---|
e69fb17b02 | |||
72f9a53ba3 | |||
cf15ff5c07 | |||
6bbdfcdfbe | |||
06e93ff520 | |||
550e7a15e6 | |||
71c54cd541 | |||
e81b8e53dc | |||
84e6928f54 | |||
ba688fe62c | |||
c533e10352 | |||
04f47b3db6 | |||
717fa5edf4 | |||
14f5f048fb | |||
72929500d3 | |||
471adde923 | |||
6c5d5f3d00 | |||
2262fef29b | |||
bda30f1475 | |||
bfec434369 | |||
14df350994 | |||
80582f5e8d | |||
7442768ced | |||
77c7bdfdec | |||
07d9769966 | |||
385b6177b2 | |||
7f68d26433 |
@ -17,7 +17,7 @@
|
||||
"screenfull": "^3.0.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"file-saver": "^1.3.3",
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter3": "^1.2.0",
|
||||
"lodash": "3.10.1",
|
||||
|
@ -33,7 +33,7 @@ requirejs.config({
|
||||
"moment": "bower_components/moment/moment",
|
||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
|
||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||
"saveAs": "bower_components/file-saver/FileSaver.min",
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
|
@ -101,10 +101,15 @@ define(
|
||||
*/
|
||||
EditorCapability.prototype.finish = function () {
|
||||
var domainObject = this.domainObject;
|
||||
return this.transactionService.cancel().then(function () {
|
||||
domainObject.getCapability("status").set("editing", false);
|
||||
return domainObject;
|
||||
});
|
||||
|
||||
if (this.transactionService.isActive()) {
|
||||
return this.transactionService.cancel().then(function () {
|
||||
domainObject.getCapability("status").set("editing", false);
|
||||
return domainObject;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(domainObject);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,6 +62,7 @@ define(
|
||||
);
|
||||
mockTransactionService.commit.andReturn(fastPromise());
|
||||
mockTransactionService.cancel.andReturn(fastPromise());
|
||||
mockTransactionService.isActive = jasmine.createSpy('isActive');
|
||||
|
||||
mockStatusCapability = jasmine.createSpyObj(
|
||||
"statusCapability",
|
||||
@ -141,6 +142,7 @@ define(
|
||||
|
||||
describe("finish", function () {
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.andReturn(true);
|
||||
capability.edit();
|
||||
capability.finish();
|
||||
});
|
||||
@ -152,6 +154,23 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
describe("finish", function () {
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.andReturn(false);
|
||||
capability.edit();
|
||||
});
|
||||
|
||||
it("does not cancel transaction when transaction is not active", function () {
|
||||
capability.finish();
|
||||
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns a promise", function () {
|
||||
expect(capability.finish() instanceof Promise).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("dirty", function () {
|
||||
var model = {};
|
||||
|
||||
|
@ -121,6 +121,9 @@ define([
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
if (typeof text === 'number') {
|
||||
return text;
|
||||
}
|
||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||
};
|
||||
|
||||
|
@ -50,7 +50,6 @@
|
||||
content:'';
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
display: inline;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,11 @@
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
background: none !important;
|
||||
margin-right: $interiorMarginSm;
|
||||
&[class*='s-status']:before {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
|
@ -23,10 +23,13 @@
|
||||
define([
|
||||
"moment-timezone",
|
||||
"./src/indicators/ClockIndicator",
|
||||
"./src/indicators/FollowIndicator",
|
||||
"./src/services/TickerService",
|
||||
"./src/services/TimerService",
|
||||
"./src/controllers/ClockController",
|
||||
"./src/controllers/TimerController",
|
||||
"./src/controllers/RefreshingController",
|
||||
"./src/actions/FollowTimerAction",
|
||||
"./src/actions/StartTimerAction",
|
||||
"./src/actions/RestartTimerAction",
|
||||
"./src/actions/StopTimerAction",
|
||||
@ -37,10 +40,13 @@ define([
|
||||
], function (
|
||||
MomentTimezone,
|
||||
ClockIndicator,
|
||||
FollowIndicator,
|
||||
TickerService,
|
||||
TimerService,
|
||||
ClockController,
|
||||
TimerController,
|
||||
RefreshingController,
|
||||
FollowTimerAction,
|
||||
StartTimerAction,
|
||||
RestartTimerAction,
|
||||
StopTimerAction,
|
||||
@ -80,6 +86,11 @@ define([
|
||||
"CLOCK_INDICATOR_FORMAT"
|
||||
],
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"implementation": FollowIndicator,
|
||||
"depends": ["timerService"],
|
||||
"priority": "fallback"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
@ -90,6 +101,11 @@ define([
|
||||
"$timeout",
|
||||
"now"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "timerService",
|
||||
"implementation": TimerService,
|
||||
"depends": ["openmct"]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
@ -134,6 +150,15 @@ define([
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "timer.follow",
|
||||
"implementation": FollowTimerAction,
|
||||
"depends": ["timerService"],
|
||||
"category": "contextual",
|
||||
"name": "Follow Timer",
|
||||
"cssClass": "icon-clock",
|
||||
"priority": "optional"
|
||||
},
|
||||
{
|
||||
"key": "timer.start",
|
||||
"implementation": StartTimerAction,
|
||||
|
56
platform/features/clock/src/actions/FollowTimerAction.js
Normal file
56
platform/features/clock/src/actions/FollowTimerAction.js
Normal file
@ -0,0 +1,56 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Designates a specific timer for following. Timelines, for example,
|
||||
* use the actively followed timer to display a time-of-interest line
|
||||
* and interpret time conductor bounds in the Timeline's relative
|
||||
* time frame.
|
||||
*
|
||||
* @implements {Action}
|
||||
* @memberof platform/features/clock
|
||||
* @constructor
|
||||
* @param {ActionContext} context the context for this action
|
||||
*/
|
||||
function FollowTimerAction(timerService, context) {
|
||||
var domainObject =
|
||||
context.domainObject &&
|
||||
context.domainObject.useCapability('adapter');
|
||||
this.perform =
|
||||
timerService.setTimer.bind(timerService, domainObject);
|
||||
}
|
||||
|
||||
FollowTimerAction.appliesTo = function (context) {
|
||||
var model =
|
||||
(context.domainObject && context.domainObject.getModel()) ||
|
||||
{};
|
||||
|
||||
return model.type === 'timer';
|
||||
};
|
||||
|
||||
return FollowTimerAction;
|
||||
}
|
||||
);
|
57
platform/features/clock/src/indicators/FollowIndicator.js
Normal file
57
platform/features/clock/src/indicators/FollowIndicator.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['moment'],
|
||||
function (moment) {
|
||||
var NO_TIMER = "No timer being followed";
|
||||
|
||||
/**
|
||||
* Indicator that displays the active timer, as well as its
|
||||
* current state.
|
||||
* @implements {Indicator}
|
||||
* @memberof platform/features/clock
|
||||
*/
|
||||
function FollowIndicator(timerService) {
|
||||
this.timerService = timerService;
|
||||
}
|
||||
|
||||
FollowIndicator.prototype.getGlyphClass = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
FollowIndicator.prototype.getCssClass = function () {
|
||||
return (this.timerService.getTimer()) ? "icon-timer s-status-ok" : "icon-timer";
|
||||
};
|
||||
|
||||
FollowIndicator.prototype.getText = function () {
|
||||
var timer = this.timerService.getTimer();
|
||||
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
|
||||
};
|
||||
|
||||
FollowIndicator.prototype.getDescription = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
return FollowIndicator;
|
||||
}
|
||||
);
|
113
platform/features/clock/src/services/TimerService.js
Normal file
113
platform/features/clock/src/services/TimerService.js
Normal file
@ -0,0 +1,113 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* Tracks the currently-followed Timer object. Used by
|
||||
* timelines et al to synchronize to a particular timer.
|
||||
*
|
||||
* The TimerService emits `change` events when the active timer
|
||||
* is changed.
|
||||
*/
|
||||
function TimerService(openmct) {
|
||||
EventEmitter.apply(this);
|
||||
this.time = openmct.time;
|
||||
this.objects = openmct.objects;
|
||||
}
|
||||
|
||||
TimerService.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Set (or clear, if `timer` is undefined) the currently active timer.
|
||||
* @param {DomainObject} timer the new active timer
|
||||
* @emits change
|
||||
*/
|
||||
TimerService.prototype.setTimer = function (timer) {
|
||||
this.timer = timer;
|
||||
this.emit('change');
|
||||
|
||||
if (this.stopObserving) {
|
||||
this.stopObserving();
|
||||
delete this.stopObserving;
|
||||
}
|
||||
|
||||
if (timer) {
|
||||
this.stopObserving =
|
||||
this.objects.observe(timer, '*', this.setTimer.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently active timer.
|
||||
* @return {DomainObject} the active timer
|
||||
* @emits change
|
||||
*/
|
||||
TimerService.prototype.getTimer = function () {
|
||||
return this.timer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if there is a currently active timer.
|
||||
* @return {boolean} true if there is a timer
|
||||
*/
|
||||
TimerService.prototype.hasTimer = function () {
|
||||
return !!this.timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the provided timestamp to milliseconds relative to
|
||||
* the active timer.
|
||||
* @return {number} milliseconds since timer start
|
||||
*/
|
||||
TimerService.prototype.convert = function (timestamp) {
|
||||
var clock = this.time.clock();
|
||||
var canConvert = this.hasTimer() &&
|
||||
!!clock &&
|
||||
this.timer.timerState !== 'stopped';
|
||||
|
||||
if (!canConvert) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var now = clock.currentValue();
|
||||
var delta = this.timer.timerState === 'paused' ?
|
||||
now - this.timer.pausedTime : 0;
|
||||
var epoch = this.timer.timestamp;
|
||||
|
||||
return timestamp - epoch - delta;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value of the active clock, adjusted to be relative to the active
|
||||
* timer. If there is no clock or no active timer, this will return
|
||||
* `undefined`.
|
||||
* @return {number} milliseconds since the start of the active timer
|
||||
*/
|
||||
TimerService.prototype.now = function () {
|
||||
var clock = this.time.clock();
|
||||
return clock && this.convert(clock.currentValue());
|
||||
};
|
||||
|
||||
return TimerService;
|
||||
});
|
@ -0,0 +1,87 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"../../src/actions/FollowTimerAction"
|
||||
], function (FollowTimerAction) {
|
||||
var TIMER_SERVICE_METHODS =
|
||||
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
|
||||
|
||||
describe("The Follow Timer action", function () {
|
||||
var testContext;
|
||||
var testModel;
|
||||
var testAdaptedObject;
|
||||
|
||||
beforeEach(function () {
|
||||
testModel = {};
|
||||
testContext = { domainObject: jasmine.createSpyObj('domainObject', [
|
||||
'getModel',
|
||||
'useCapability'
|
||||
]) };
|
||||
testAdaptedObject = { foo: 'bar' };
|
||||
testContext.domainObject.getModel.andReturn(testModel);
|
||||
testContext.domainObject.useCapability.andCallFake(function (c) {
|
||||
return c === 'adapter' && testAdaptedObject;
|
||||
});
|
||||
});
|
||||
|
||||
it("is applicable to timers", function () {
|
||||
testModel.type = "timer";
|
||||
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
|
||||
});
|
||||
|
||||
it("is inapplicable to non-timers", function () {
|
||||
testModel.type = "folder";
|
||||
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
|
||||
});
|
||||
|
||||
describe("when instantiated", function () {
|
||||
var mockTimerService;
|
||||
var action;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimerService = jasmine.createSpyObj(
|
||||
'timerService',
|
||||
TIMER_SERVICE_METHODS
|
||||
);
|
||||
action = new FollowTimerAction(mockTimerService, testContext);
|
||||
});
|
||||
|
||||
it("does not interact with the timer service", function () {
|
||||
TIMER_SERVICE_METHODS.forEach(function (method) {
|
||||
expect(mockTimerService[method]).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and performed", function () {
|
||||
beforeEach(function () {
|
||||
action.perform();
|
||||
});
|
||||
|
||||
it("sets the active timer", function () {
|
||||
expect(mockTimerService.setTimer)
|
||||
.toHaveBeenCalledWith(testAdaptedObject);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,61 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
|
||||
var TIMER_SERVICE_METHODS =
|
||||
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
|
||||
|
||||
describe("The timer-following indicator", function () {
|
||||
var mockTimerService;
|
||||
var indicator;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimerService =
|
||||
jasmine.createSpyObj('timerService', TIMER_SERVICE_METHODS);
|
||||
indicator = new FollowIndicator(mockTimerService);
|
||||
});
|
||||
|
||||
it("implements the Indicator interface", function () {
|
||||
expect(indicator.getGlyphClass()).toEqual(jasmine.any(String));
|
||||
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
|
||||
expect(indicator.getText()).toEqual(jasmine.any(String));
|
||||
expect(indicator.getDescription()).toEqual(jasmine.any(String));
|
||||
});
|
||||
|
||||
describe("when a timer is set", function () {
|
||||
var testModel;
|
||||
var mockDomainObject;
|
||||
|
||||
beforeEach(function () {
|
||||
testModel = { name: "some timer!" };
|
||||
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
|
||||
mockDomainObject.getModel.andReturn(testModel);
|
||||
mockTimerService.getTimer.andReturn(mockDomainObject);
|
||||
});
|
||||
|
||||
it("displays the timer's name", function () {
|
||||
expect(indicator.getText().indexOf(testModel.name))
|
||||
.not.toEqual(-1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
77
platform/features/clock/test/services/TimerServiceSpec.js
Normal file
77
platform/features/clock/test/services/TimerServiceSpec.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../../src/services/TimerService'
|
||||
], function (TimerService) {
|
||||
describe("TimerService", function () {
|
||||
var callback;
|
||||
var mockmct;
|
||||
var timerService;
|
||||
|
||||
beforeEach(function () {
|
||||
callback = jasmine.createSpy('callback');
|
||||
mockmct = {
|
||||
time: { clock: jasmine.createSpy('clock') },
|
||||
objects: { observe: jasmine.createSpy('observe') }
|
||||
};
|
||||
timerService = new TimerService(mockmct);
|
||||
timerService.on('change', callback);
|
||||
});
|
||||
|
||||
it("initially emits no change events", function () {
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports no current timer", function () {
|
||||
expect(timerService.getTimer()).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("setTimer", function () {
|
||||
var testTimer;
|
||||
|
||||
beforeEach(function () {
|
||||
testTimer = { name: "I am some timer; you are nobody." };
|
||||
timerService.setTimer(testTimer);
|
||||
});
|
||||
|
||||
it("emits a change event", function () {
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports the current timer", function () {
|
||||
expect(timerService.getTimer()).toBe(testTimer);
|
||||
});
|
||||
|
||||
it("observes changes to an object", function () {
|
||||
var newTimer = { name: "I am another timer." };
|
||||
expect(mockmct.objects.observe).toHaveBeenCalledWith(
|
||||
testTimer,
|
||||
'*',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
mockmct.objects.observe.mostRecentCall.args[2](newTimer);
|
||||
expect(timerService.getTimer()).toBe(newTimer);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -255,6 +255,8 @@ define(
|
||||
if (this.nextDatum) {
|
||||
this.updateValues(this.nextDatum);
|
||||
delete this.nextDatum;
|
||||
} else {
|
||||
this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
|
||||
}
|
||||
this.autoScroll = true;
|
||||
}
|
||||
|
@ -183,6 +183,17 @@ define(
|
||||
expect(controller.getImageUrl()).toEqual(newUrl);
|
||||
});
|
||||
|
||||
it("forwards large image view to latest image in history on un-pause", function () {
|
||||
$scope.imageHistory = [
|
||||
{ utc: 1434600258122, url: 'some/url1', selected: false},
|
||||
{ utc: 1434600258123, url: 'some/url2', selected: false}
|
||||
];
|
||||
controller.paused(true);
|
||||
controller.paused(false);
|
||||
|
||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
|
||||
});
|
||||
|
||||
it("subscribes to telemetry", function () {
|
||||
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
||||
newDomainObject,
|
||||
@ -227,7 +238,7 @@ define(
|
||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
||||
});
|
||||
|
||||
describe("user clicks on imagery thumbnail", function () {
|
||||
describe("when user clicks on imagery thumbnail", function () {
|
||||
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
||||
|
||||
it("pauses and adds selected class to imagery thumbnail", function () {
|
||||
@ -248,6 +259,7 @@ define(
|
||||
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("initially shows an empty string for date/time", function () {
|
||||
|
@ -360,22 +360,47 @@ define(
|
||||
*/
|
||||
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
||||
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
var rangeMetadata = metadata.valuesForHints(['range'])[0];
|
||||
var rangeKey = rangeMetadata.source || rangeMetadata.key;
|
||||
var valueMetadata = metadata.value(rangeKey);
|
||||
var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata);
|
||||
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata);
|
||||
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
||||
var value = datum[valueMetadata.key];
|
||||
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, rangeKey);
|
||||
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay);
|
||||
|
||||
this.setDisplayedValue(
|
||||
telemetryObject,
|
||||
formatter.format(value),
|
||||
formattedTelemetryValue,
|
||||
alarm && alarm.cssClass
|
||||
);
|
||||
this.digest();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
FixedController.prototype.getFormattedTelemetryValueForKey = function (telemetryKeyToDisplay, datum, metadata) {
|
||||
var valueMetadata = metadata.value(telemetryKeyToDisplay);
|
||||
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
||||
|
||||
return formatter.format(datum[valueMetadata.key]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) {
|
||||
// If there is a range value, show that preferentially
|
||||
var telemetryKeyToDisplay = metadata.valuesForHints(['range'])[0];
|
||||
|
||||
// If no range is defined, default to the highest priority non time-domain data.
|
||||
if (telemetryKeyToDisplay === undefined) {
|
||||
var valuesOrderedByPriority = metadata.values();
|
||||
telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) {
|
||||
return !(valueMetadata.hints.domain);
|
||||
})[0];
|
||||
}
|
||||
|
||||
return telemetryKeyToDisplay.source;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request the last historical data point for the given domain objects
|
||||
* @param {object[]} objects
|
||||
@ -388,7 +413,9 @@ define(
|
||||
objects.forEach(function (object) {
|
||||
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
||||
.then(function (data) {
|
||||
self.updateView(object, data[data.length - 1]);
|
||||
if (data.length > 0) {
|
||||
self.updateView(object, data[data.length - 1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return objects;
|
||||
|
@ -127,9 +127,7 @@ define(
|
||||
if (self.droppedIdToSelectAfterRefresh) {
|
||||
self.select(null, self.droppedIdToSelectAfterRefresh);
|
||||
delete self.droppedIdToSelectAfterRefresh;
|
||||
}
|
||||
|
||||
if (composition.indexOf(self.selectedId) === -1) {
|
||||
} else if (composition.indexOf(self.selectedId) === -1) {
|
||||
self.clearSelection();
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,6 @@ define(
|
||||
Promise.resolve(mockChildren)
|
||||
);
|
||||
|
||||
|
||||
mockScope.model = testModel;
|
||||
mockScope.configuration = testConfiguration;
|
||||
mockScope.selection = jasmine.createSpyObj(
|
||||
@ -194,7 +193,8 @@ define(
|
||||
|
||||
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
||||
'valuesForHints',
|
||||
'value'
|
||||
'value',
|
||||
'values'
|
||||
]);
|
||||
mockMetadata.value.andReturn({
|
||||
key: 'value'
|
||||
@ -653,6 +653,39 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("selects an range value to display, if available", function () {
|
||||
mockMetadata.valuesForHints.andReturn([
|
||||
{
|
||||
key: 'range',
|
||||
source: 'range'
|
||||
}
|
||||
]);
|
||||
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
||||
expect(key).toEqual('range');
|
||||
});
|
||||
|
||||
it("selects the first non-domain value to display, if no range available", function () {
|
||||
mockMetadata.valuesForHints.andReturn([]);
|
||||
mockMetadata.values.andReturn([
|
||||
{
|
||||
key: 'domain',
|
||||
source: 'domain',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
source: 'image',
|
||||
hints: {
|
||||
image: 1
|
||||
}
|
||||
}
|
||||
]);
|
||||
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
||||
expect(key).toEqual('image');
|
||||
});
|
||||
|
||||
it("reflects limit status", function () {
|
||||
mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
|
||||
controller.updateView(mockTelemetryObject, [{
|
||||
|
@ -49,7 +49,7 @@
|
||||
{
|
||||
"key": "ListViewController",
|
||||
"implementation": ListViewController,
|
||||
"depends": ["$scope"]
|
||||
"depends": ["$scope", "formatService"]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(function () {
|
||||
function ListViewController($scope) {
|
||||
function ListViewController($scope, formatService) {
|
||||
this.$scope = $scope;
|
||||
$scope.orderByField = 'title';
|
||||
$scope.reverseSort = false;
|
||||
@ -30,6 +30,8 @@ define(function () {
|
||||
var unlisten = $scope.domainObject.getCapability('mutation')
|
||||
.listen(this.updateView.bind(this));
|
||||
|
||||
this.utc = formatService.getFormat('utc');
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
unlisten();
|
||||
});
|
||||
@ -50,17 +52,13 @@ define(function () {
|
||||
icon: child.getCapability('type').getCssClass(),
|
||||
title: child.getModel().name,
|
||||
type: child.getCapability('type').getName(),
|
||||
persisted: new Date(
|
||||
child.getModel().persisted
|
||||
).toUTCString(),
|
||||
modified: new Date(
|
||||
child.getModel().modified
|
||||
).toUTCString(),
|
||||
persisted: this.utc.format(child.getModel().persisted),
|
||||
modified: this.utc.format(child.getModel().modified),
|
||||
asDomainObject: child,
|
||||
location: child.getCapability('location'),
|
||||
action: child.getCapability('action')
|
||||
};
|
||||
});
|
||||
}, this);
|
||||
};
|
||||
|
||||
return ListViewController;
|
||||
|
@ -31,7 +31,9 @@ define(
|
||||
controller,
|
||||
childModel,
|
||||
typeCapability,
|
||||
mutationCapability;
|
||||
mutationCapability,
|
||||
formatService;
|
||||
|
||||
beforeEach(function () {
|
||||
unlistenFunc = jasmine.createSpy("unlisten");
|
||||
|
||||
@ -41,6 +43,18 @@ define(
|
||||
);
|
||||
mutationCapability.listen.andReturn(unlistenFunc);
|
||||
|
||||
formatService = jasmine.createSpyObj(
|
||||
"formatService",
|
||||
["getFormat"]
|
||||
);
|
||||
formatService.getFormat.andReturn(jasmine.createSpyObj(
|
||||
'utc',
|
||||
["format"]
|
||||
));
|
||||
formatService.getFormat().format.andCallFake(function (v) {
|
||||
return "formatted " + v;
|
||||
});
|
||||
|
||||
typeCapability = jasmine.createSpyObj(
|
||||
"typeCapability",
|
||||
["getCssClass", "getName"]
|
||||
@ -94,20 +108,27 @@ define(
|
||||
);
|
||||
scope.domainObject = domainObject;
|
||||
|
||||
controller = new ListViewController(scope);
|
||||
controller = new ListViewController(scope, formatService);
|
||||
|
||||
waitsFor(function () {
|
||||
return scope.children;
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the UTC time format", function () {
|
||||
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
|
||||
});
|
||||
|
||||
it("updates the view", function () {
|
||||
expect(scope.children[0]).toEqual(
|
||||
{
|
||||
icon: "icon-folder",
|
||||
title: "Battery Charge Status",
|
||||
type: "Folder",
|
||||
persisted: "Wed, 07 Jun 2017 20:34:57 GMT",
|
||||
modified: "Wed, 07 Jun 2017 20:34:57 GMT",
|
||||
persisted: formatService.getFormat('utc')
|
||||
.format(childModel.persisted),
|
||||
modified: formatService.getFormat('utc')
|
||||
.format(childModel.modified),
|
||||
asDomainObject: childObject,
|
||||
location: ''
|
||||
}
|
||||
|
@ -170,6 +170,9 @@ define(
|
||||
* @param rows
|
||||
*/
|
||||
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
||||
rows.forEach(function (row) {
|
||||
this.$scope.rows.push(row);
|
||||
}, this);
|
||||
this.$scope.$broadcast('add:rows', rows);
|
||||
};
|
||||
|
||||
|
@ -436,5 +436,28 @@ define(
|
||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
|
||||
});
|
||||
|
||||
describe('when telemetry is added', function () {
|
||||
var testRows;
|
||||
var expectedRows;
|
||||
|
||||
beforeEach(function () {
|
||||
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
||||
mockScope.rows = [{ a: -1 }];
|
||||
expectedRows = mockScope.rows.concat(testRows);
|
||||
|
||||
spyOn(controller.telemetry, "on").andCallThrough();
|
||||
controller.registerChangeListeners();
|
||||
|
||||
controller.telemetry.on.calls.forEach(function (call) {
|
||||
if (call.args[0] === 'added') {
|
||||
call.args[1](testRows);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("adds it to rows in scope", function () {
|
||||
expect(mockScope.rows).toEqual(expectedRows);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,7 @@ define([
|
||||
"./src/controllers/TimelineTickController",
|
||||
"./src/controllers/TimelineTableController",
|
||||
"./src/controllers/TimelineGanttController",
|
||||
"./src/controllers/TimelineTOIController",
|
||||
"./src/controllers/ActivityModeValuesController",
|
||||
"./src/capabilities/ActivityTimespanCapability",
|
||||
"./src/capabilities/TimelineTimespanCapability",
|
||||
@ -37,6 +38,7 @@ define([
|
||||
"./src/capabilities/CostCapability",
|
||||
"./src/directives/MCTSwimlaneDrop",
|
||||
"./src/directives/MCTSwimlaneDrag",
|
||||
"./src/directives/MCTResourceGraphDrop",
|
||||
"./src/services/ObjectLoader",
|
||||
"./src/chart/MCTTimelineChart",
|
||||
"text!./res/templates/values.html",
|
||||
@ -59,6 +61,7 @@ define([
|
||||
TimelineTickController,
|
||||
TimelineTableController,
|
||||
TimelineGanttController,
|
||||
TimelineTOIController,
|
||||
ActivityModeValuesController,
|
||||
ActivityTimespanCapability,
|
||||
TimelineTimespanCapability,
|
||||
@ -67,6 +70,7 @@ define([
|
||||
CostCapability,
|
||||
MCTSwimlaneDrop,
|
||||
MCTSwimlaneDrag,
|
||||
MCTResourceGraphDrop,
|
||||
ObjectLoader,
|
||||
MCTTimelineChart,
|
||||
valuesTemplate,
|
||||
@ -502,6 +506,15 @@ define([
|
||||
"TIMELINE_MAXIMUM_OFFSCREEN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TimelineTOIController",
|
||||
"implementation": TimelineTOIController,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"timerService",
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ActivityModeValuesController",
|
||||
"implementation": ActivityModeValuesController,
|
||||
@ -566,6 +579,13 @@ define([
|
||||
"$interval",
|
||||
"$log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctResourceGraphDrop",
|
||||
"implementation": MCTResourceGraphDrop,
|
||||
"depends": [
|
||||
"dndService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
|
@ -29,6 +29,44 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Follow Line
|
||||
.l-follow-line {
|
||||
// TODO: move before and after into l-timeline-gantt so those only render in that pane
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
width: 1px;
|
||||
z-index: 9; // Just below .l-hover-btns-holder
|
||||
}
|
||||
}
|
||||
|
||||
.l-timeline-gantt {
|
||||
.l-follow-line {
|
||||
$d: 0.8rem;
|
||||
top: $interiorMargin;
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: $d;
|
||||
width: $d;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@include transform(translateX(-50%));
|
||||
}
|
||||
&:before {
|
||||
// Icon blocker
|
||||
width: 2 * $d;
|
||||
}
|
||||
&:after {
|
||||
// Icon
|
||||
font-size: $d;
|
||||
line-height: $d;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.s-timeline-gantt {
|
||||
@ -108,10 +146,9 @@
|
||||
}
|
||||
.s-hover-btns-holder {
|
||||
$bg: $timelineHeaderColorBg;
|
||||
$bga: 1;
|
||||
$l: 5%;
|
||||
@include user-select(none);
|
||||
@include background-image(linear-gradient(-90deg, rgba($bg, $bga), rgba($bg, $bga) 70%, rgba($bg, 0) 100%));
|
||||
@include background-image(linear-gradient(-90deg, rgba($bg, 1), rgba($bg, 1) 70%, rgba($bg, 0) 100%));
|
||||
.s-button {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
@ -129,4 +166,27 @@
|
||||
color: $timelineResourceGraphFg;
|
||||
}
|
||||
}
|
||||
|
||||
.s-follow-line {
|
||||
background: rgba($timeControllerToiLineColor, 0.5);
|
||||
}
|
||||
|
||||
.s-timeline-gantt {
|
||||
.s-follow-line {
|
||||
&:after {
|
||||
// Icon
|
||||
color: $timeControllerToiLineColor;
|
||||
content: $glyph-icon-timer;
|
||||
font-family: symbolsfont;
|
||||
text-shadow: $shdwItemText;
|
||||
}
|
||||
&:before {
|
||||
// Blocker
|
||||
$bg: $timelineHeaderColorBg;
|
||||
$l: 30%;
|
||||
@include background-image(linear-gradient(90deg, rgba($bg, 0), rgba($bg, 1) $l, rgba($bg, 1) 100% - $l, rgba($bg, 0)));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,10 @@
|
||||
.l-timeline-pane {
|
||||
@include absPosDefault();
|
||||
|
||||
&.drop-over {
|
||||
background-color: lighten($colorEditAreaBg, 5%);
|
||||
}
|
||||
|
||||
.l-width-control {
|
||||
position: relative;
|
||||
}
|
||||
@ -75,6 +79,10 @@
|
||||
}
|
||||
}
|
||||
&.l-timeline-gantt {
|
||||
.abs.l-timeline-gantt-header-w {
|
||||
overflow: hidden;
|
||||
height: $timelineTopPaneHeaderH;
|
||||
}
|
||||
.l-swimlanes-holder {
|
||||
@include scrollV(scroll);
|
||||
bottom: $scrollbarTrackSize;
|
||||
|
@ -24,6 +24,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
|
||||
@import "../../../../commonUI/general/res/sass/constants";
|
||||
@import "../../../../commonUI/general/res/sass/mixins";
|
||||
@import "../../../../commonUI/general/res/sass/glyphs";
|
||||
@import "../../../../commonUI/themes/espresso/res/sass/constants";
|
||||
@import "../../../../commonUI/themes/espresso/res/sass/mixins";
|
||||
@import "constants";
|
||||
|
@ -24,6 +24,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
|
||||
@import "../../../../commonUI/general/res/sass/constants";
|
||||
@import "../../../../commonUI/general/res/sass/mixins";
|
||||
@import "../../../../commonUI/general/res/sass/glyphs";
|
||||
@import "../../../../commonUI/themes/snow/res/sass/constants";
|
||||
@import "../../../../commonUI/themes/snow/res/sass/mixins";
|
||||
@import "constants";
|
||||
|
@ -24,6 +24,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
|
||||
@import "../../../../commonUI/general/res/sass/constants";
|
||||
@import "../../../../commonUI/general/res/sass/mixins";
|
||||
@import "../../../../commonUI/general/res/sass/glyphs";
|
||||
@import "../../../../commonUI/themes/espresso/res/sass/constants";
|
||||
@import "../../../../commonUI/themes/espresso/res/sass/mixins";
|
||||
@import "constants";
|
||||
|
@ -77,7 +77,8 @@
|
||||
<mct-splitter></mct-splitter>
|
||||
|
||||
<!-- BOTTOM PANE RESOURCE LEGEND -->
|
||||
<div class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
|
||||
<div mct-resource-graph-drop
|
||||
class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
|
||||
<div class="l-title s-title">{{ngModel.title}}Resource Graph Legend</div>
|
||||
<div class="l-legend-items legend">
|
||||
<mct-include key="'timeline-legend-item'"
|
||||
@ -96,109 +97,124 @@
|
||||
|
||||
<!-- RIGHT PANE: GANTT AND RESOURCE PLOTS -->
|
||||
<span ng-controller="TimelineZoomController as zoomController" class="abs">
|
||||
<mct-split-pane anchor="bottom"
|
||||
|
||||
<span class="toi-control-holder temp" ng-controller="TimelineTOIController as toiController">
|
||||
<mct-split-pane anchor="bottom"
|
||||
position="pane.y"
|
||||
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v">
|
||||
|
||||
<!-- TOP PANE GANTT BARS -->
|
||||
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
|
||||
<div class="l-hover-btns-holder s-hover-btns-holder">
|
||||
<a class="s-button icon-arrows-out"
|
||||
ng-click="zoomController.fit()"
|
||||
ng-show="true"
|
||||
title="Zoom to fit">
|
||||
</a>
|
||||
<!-- TOP PANE GANTT BARS -->
|
||||
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
|
||||
<div class="l-hover-btns-holder s-hover-btns-holder">
|
||||
<a class="s-button icon-timer"
|
||||
ng-click="scroll.follow = true"
|
||||
ng-show="!toiController.isFollowing() && toiController.isActive()"
|
||||
title="Follow time bounds">
|
||||
</a>
|
||||
|
||||
<a class="s-button icon-magnify-in"
|
||||
ng-click="zoomController.zoom(-1)"
|
||||
ng-show="true"
|
||||
title="Zoom in">
|
||||
</a>
|
||||
<a class="s-button icon-arrows-out"
|
||||
ng-click="scroll.follow = false; zoomController.fit()"
|
||||
ng-show="true"
|
||||
title="Zoom to fit">
|
||||
</a>
|
||||
|
||||
<a class="s-button icon-magnify-out"
|
||||
ng-click="zoomController.zoom(1)"
|
||||
ng-show="true"
|
||||
title="Zoom out">
|
||||
</a>
|
||||
</div>
|
||||
<a class="s-button icon-magnify-in"
|
||||
ng-click="scroll.follow = false; zoomController.zoom(-1)"
|
||||
ng-show="true"
|
||||
title="Zoom in">
|
||||
</a>
|
||||
|
||||
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
|
||||
<mct-include key="'timeline-ticks'"
|
||||
parameters="{
|
||||
fullWidth: zoomController.width(timelineController.end()),
|
||||
start: scroll.x,
|
||||
width: scroll.width,
|
||||
step: zoomController.toPixels(zoomController.zoom()),
|
||||
toMillis: zoomController.toMillis
|
||||
}">
|
||||
</mct-include>
|
||||
</div>
|
||||
<a class="s-button icon-magnify-out"
|
||||
ng-click="scroll.follow = false; zoomController.zoom(1)"
|
||||
ng-show="true"
|
||||
title="Zoom out">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="t-swimlanes-holder l-swimlanes-holder"
|
||||
mct-scroll-x="scroll.x"
|
||||
mct-scroll-y="scroll.y">
|
||||
<div class="l-width-control"
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||
<div class="t-swimlane s-swimlane l-swimlane"
|
||||
ng-repeat="swimlane in timelineController.swimlanes()"
|
||||
ng-class="{
|
||||
exceeded: swimlane.exceeded(),
|
||||
selected: selection.selected(swimlane),
|
||||
'drop-into': swimlane.highlight(),
|
||||
'drop-after': swimlane.highlightBottom()
|
||||
}"
|
||||
ng-click="selection.select(swimlane)"
|
||||
mct-swimlane-drop="swimlane">
|
||||
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
|
||||
<mct-include key="'timeline-ticks'"
|
||||
parameters="{
|
||||
fullWidth: zoomController.width(timelineController.end()),
|
||||
start: scroll.x,
|
||||
width: scroll.width,
|
||||
step: zoomController.toPixels(zoomController.zoom()),
|
||||
toMillis: zoomController.toMillis
|
||||
}">
|
||||
</mct-include>
|
||||
</div>
|
||||
<div ng-if="toiController.isActive()" class="l-follow-line s-follow-line"
|
||||
ng-style="{ left: toiController.x() - scroll.x + 'px' }"></div>
|
||||
|
||||
<mct-representation key="'gantt'"
|
||||
mct-object="swimlane.domainObject"
|
||||
parameters="{
|
||||
scroll: scroll,
|
||||
toPixels: zoomController.toPixels
|
||||
}">
|
||||
</mct-representation>
|
||||
<div class="t-swimlanes-holder l-swimlanes-holder"
|
||||
mct-scroll-x="scroll.x"
|
||||
mct-scroll-y="scroll.y">
|
||||
<div class="l-width-control"
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||
<div class="t-swimlane s-swimlane l-swimlane"
|
||||
ng-repeat="swimlane in timelineController.swimlanes()"
|
||||
ng-class="{
|
||||
exceeded: swimlane.exceeded(),
|
||||
selected: selection.selected(swimlane),
|
||||
'drop-into': swimlane.highlight(),
|
||||
'drop-after': swimlane.highlightBottom()
|
||||
}"
|
||||
ng-click="selection.select(swimlane)"
|
||||
mct-swimlane-drop="swimlane">
|
||||
|
||||
<span ng-if="selection.selected(swimlane)">
|
||||
<span ng-repeat="handle in timelineController.handles()"
|
||||
ng-style="handle.style(zoomController)"
|
||||
style="position: absolute; top: 0px; bottom: 0px;"
|
||||
class="handle"
|
||||
ng-class="{ start: $index === 0, mid: $index === 1, end: $index > 1 }"
|
||||
mct-drag-down="handle.begin()"
|
||||
mct-drag="handle.drag(delta[0], zoomController); timelineController.refresh()"
|
||||
mct-drag-up="handle.finish()">
|
||||
</span>
|
||||
</span>
|
||||
<mct-representation key="'gantt'"
|
||||
mct-object="swimlane.domainObject"
|
||||
parameters="{
|
||||
scroll: scroll,
|
||||
toPixels: zoomController.toPixels
|
||||
}">
|
||||
</mct-representation>
|
||||
|
||||
<span ng-if="selection.selected(swimlane)">
|
||||
<span ng-repeat="handle in timelineController.handles()"
|
||||
ng-style="handle.style(zoomController)"
|
||||
style="position: absolute; top: 0px; bottom: 0px;"
|
||||
class="handle"
|
||||
ng-class="{ start: $index === 0, mid: $index === 1, end: $index > 1 }"
|
||||
mct-drag-down="handle.begin()"
|
||||
mct-drag="handle.drag(delta[0], zoomController); timelineController.refresh()"
|
||||
mct-drag-up="handle.finish()">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HORZ SPLITTER -->
|
||||
<mct-splitter></mct-splitter>
|
||||
<!-- HORZ SPLITTER -->
|
||||
<mct-splitter></mct-splitter>
|
||||
|
||||
<!-- BOTTOM PANE RESOURCE GRAPHS AND RIGHT PANE HORIZONTAL SCROLL CONTROL -->
|
||||
<div class="split-pane-component l-timeline-resource-graph l-timeline-pane t-pane-h l-pane-btm">
|
||||
<div class="l-graphs-holder"
|
||||
mct-resize="scroll.width = bounds.width">
|
||||
<div class="t-graphs l-graphs">
|
||||
<mct-include key="'timeline-resource-graphs'"
|
||||
parameters="{
|
||||
origin: zoomController.toMillis(scroll.x),
|
||||
duration: zoomController.toMillis(scroll.width),
|
||||
graphs: timelineController.graphs()
|
||||
}">
|
||||
</mct-include>
|
||||
<!-- BOTTOM PANE RESOURCE GRAPHS AND RIGHT PANE HORIZONTAL SCROLL CONTROL -->
|
||||
<div class="split-pane-component l-timeline-resource-graph l-timeline-pane t-pane-h l-pane-btm">
|
||||
<div class="l-graphs-holder"
|
||||
mct-resize="scroll.width = bounds.width">
|
||||
<div class="t-graphs l-graphs">
|
||||
<mct-include key="'timeline-resource-graphs'"
|
||||
parameters="{
|
||||
origin: zoomController.toMillis(scroll.x),
|
||||
duration: zoomController.toMillis(scroll.width),
|
||||
graphs: timelineController.graphs()
|
||||
}">
|
||||
</mct-include>
|
||||
</div>
|
||||
<div ng-if="toiController.isActive()" class="l-follow-line s-follow-line"
|
||||
ng-style="{ left: toiController.x() - scroll.x + 'px' }"></div>
|
||||
</div>
|
||||
<div mct-scroll-x="scroll.x"
|
||||
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
|
||||
<div class="l-width-control"
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div mct-scroll-x="scroll.x"
|
||||
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
|
||||
<div class="l-width-control"
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mct-split-pane>
|
||||
</mct-split-pane>
|
||||
</span>
|
||||
|
||||
|
||||
</span>
|
||||
</mct-split-pane>
|
||||
</div>
|
||||
|
@ -0,0 +1,111 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* Tracks time-of-interest in timelines, updating both scroll state
|
||||
* (when appropriate) and positioning of the displayed line.
|
||||
*/
|
||||
function TimelineTOIController(openmct, timerService, $scope) {
|
||||
this.openmct = openmct;
|
||||
this.timerService = timerService;
|
||||
this.$scope = $scope;
|
||||
|
||||
this.change = this.change.bind(this);
|
||||
this.bounds = this.bounds.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
this.timerService.on('change', this.change);
|
||||
this.openmct.time.on('bounds', this.bounds);
|
||||
|
||||
this.$scope.$on('$destroy', this.destroy);
|
||||
|
||||
this.$scope.scroll.follow = this.timerService.hasTimer();
|
||||
if (this.$scope.zoomController) {
|
||||
this.bounds(this.openmct.time.bounds());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a `change` event from the timer service; track the
|
||||
* new timer.
|
||||
*/
|
||||
TimelineTOIController.prototype.change = function () {
|
||||
this.$scope.scroll.follow =
|
||||
this.$scope.scroll.follow || this.timerService.hasTimer();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a `bounds` event from the time API; scroll the timeline
|
||||
* to match the current bounds, if currently in follow mode.
|
||||
*/
|
||||
TimelineTOIController.prototype.bounds = function (bounds) {
|
||||
if (this.isFollowing()) {
|
||||
var start = this.timerService.convert(bounds.start);
|
||||
var end = this.timerService.convert(bounds.end);
|
||||
this.duration = bounds.end - bounds.start;
|
||||
this.$scope.zoomController.bounds(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a `$destroy` event from scope; detach all observers.
|
||||
*/
|
||||
TimelineTOIController.prototype.destroy = function () {
|
||||
this.timerService.off('change', this.change);
|
||||
this.openmct.time.off('bounds', this.bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the x position of the time-of-interest line,
|
||||
* in pixels from the left edge of the timeline area.
|
||||
*/
|
||||
TimelineTOIController.prototype.x = function () {
|
||||
var now = this.timerService.now();
|
||||
|
||||
if (now === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.$scope.zoomController.toPixels(this.timerService.now());
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if there is an active time-of-interest to be shown.
|
||||
* @return {boolean} true when active
|
||||
*/
|
||||
TimelineTOIController.prototype.isActive = function () {
|
||||
return this.x() !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the timeline should be following time conductor bounds.
|
||||
* @return {boolean} true when following
|
||||
*/
|
||||
TimelineTOIController.prototype.isFollowing = function () {
|
||||
return !!this.$scope.scroll.follow && this.timerService.now() !== undefined;
|
||||
};
|
||||
|
||||
return TimelineTOIController;
|
||||
});
|
@ -32,7 +32,8 @@ define(
|
||||
// Prefer to start with the middle index
|
||||
var zoomLevels = ZOOM_CONFIGURATION.levels || [1000],
|
||||
zoomIndex = Math.floor(zoomLevels.length / 2),
|
||||
tickWidth = ZOOM_CONFIGURATION.width || 200;
|
||||
tickWidth = ZOOM_CONFIGURATION.width || 200,
|
||||
lastWidth = Number.MAX_VALUE; // Don't constrain prematurely
|
||||
|
||||
function toMillis(pixels) {
|
||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||
@ -55,19 +56,29 @@ define(
|
||||
|
||||
function setScroll(x) {
|
||||
$window.requestAnimationFrame(function () {
|
||||
$scope.scroll.x = x;
|
||||
$scope.scroll.x = Math.min(
|
||||
Math.max(x, 0),
|
||||
lastWidth - $scope.scroll.width
|
||||
);
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeZoomFromTimespan(timespan) {
|
||||
var timelineDuration = timespan.getDuration();
|
||||
function initializeZoomFromStartEnd(start, end) {
|
||||
var duration = end - start;
|
||||
zoomIndex = 0;
|
||||
while (toMillis($scope.scroll.width) < timelineDuration &&
|
||||
while (toMillis($scope.scroll.width) < duration &&
|
||||
zoomIndex < zoomLevels.length - 1) {
|
||||
zoomIndex += 1;
|
||||
}
|
||||
setScroll(toPixels(timespan.getStart()));
|
||||
setScroll(toPixels(start));
|
||||
}
|
||||
|
||||
function initializeZoomFromTimespan(timespan) {
|
||||
return initializeZoomFromStartEnd(
|
||||
timespan.getStart(),
|
||||
timespan.getEnd()
|
||||
);
|
||||
}
|
||||
|
||||
function initializeZoom() {
|
||||
@ -101,6 +112,13 @@ define(
|
||||
}
|
||||
return zoomLevels[zoomIndex];
|
||||
},
|
||||
/**
|
||||
* Adjust the current zoom bounds to fit both the
|
||||
* start and the end time provided.
|
||||
* @param {number} start the starting timestamp
|
||||
* @param {number} end the ending timestamp
|
||||
*/
|
||||
bounds: initializeZoomFromStartEnd,
|
||||
/**
|
||||
* Set the zoom level to fit the bounds of the timeline
|
||||
* being viewed.
|
||||
@ -119,14 +137,14 @@ define(
|
||||
*/
|
||||
toMillis: toMillis,
|
||||
/**
|
||||
* Get the pixel width necessary to fit the specified
|
||||
* timestamp, expressed as an offset in milliseconds from
|
||||
* the start of the timeline.
|
||||
* Set the maximum timestamp value to be displayed, and get
|
||||
* the pixel width necessary to display this value.
|
||||
* @param {number} timestamp the time to display
|
||||
*/
|
||||
width: function (timestamp) {
|
||||
var pixels = Math.ceil(toPixels(timestamp * (1 + PADDING)));
|
||||
return Math.max($scope.scroll.width, pixels);
|
||||
lastWidth = Math.max($scope.scroll.width, pixels);
|
||||
return lastWidth;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['./SwimlaneDragConstants'],
|
||||
function (SwimlaneDragConstants) {
|
||||
|
||||
/**
|
||||
* Defines the `mct-resource-graph-drop` directive. When a drop occurs
|
||||
* on an element with this attribute, the swimlane targeted by the drop
|
||||
* will receive the dropped domain object (at which point it can handle
|
||||
* the drop, typically by toggling the swimlane graph.)
|
||||
* @param {DndService} dndService drag-and-drop service
|
||||
*/
|
||||
function MCTResourceGraphDrop(dndService) {
|
||||
|
||||
function link(scope, element, attrs) {
|
||||
// Handle dragover
|
||||
element.on('dragover', function (e) {
|
||||
var swimlane = dndService.getData(
|
||||
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
|
||||
);
|
||||
|
||||
if (typeof swimlane !== "undefined" && !swimlane.graph()) {
|
||||
element.addClass('drop-over');
|
||||
scope.$apply();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
// Handle drops
|
||||
element.on('drop', function (e) {
|
||||
var swimlane = dndService.getData(
|
||||
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
|
||||
);
|
||||
|
||||
element.removeClass('drop-over');
|
||||
|
||||
// Only toggle if the graph isn't already set
|
||||
if (typeof swimlane !== "undefined" && !swimlane.graph()) {
|
||||
swimlane.toggleGraph();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
// Clear highlights when drag leaves this swimlane
|
||||
element.on('dragleave', function (e) {
|
||||
element.removeClass('drop-over');
|
||||
scope.$apply();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// Applies to attributes
|
||||
restrict: "A",
|
||||
// Link using above function
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTResourceGraphDrop;
|
||||
}
|
||||
);
|
@ -0,0 +1,138 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"../../src/controllers/TimelineTOIController",
|
||||
"EventEmitter"
|
||||
], function (TimelineTOIController, EventEmitter) {
|
||||
describe("The timeline TOI controller", function () {
|
||||
var mockmct;
|
||||
var mockTimerService;
|
||||
var mockScope;
|
||||
var controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockmct = { time: new EventEmitter() };
|
||||
mockmct.time.bounds = jasmine.createSpy('bounds');
|
||||
mockTimerService = new EventEmitter();
|
||||
mockTimerService.getTimer = jasmine.createSpy('getTimer');
|
||||
mockTimerService.hasTimer = jasmine.createSpy('hasTimer');
|
||||
mockTimerService.now = jasmine.createSpy('now');
|
||||
mockTimerService.convert = jasmine.createSpy('convert');
|
||||
mockScope = new EventEmitter();
|
||||
mockScope.$on = mockScope.on.bind(mockScope);
|
||||
mockScope.zoomController = jasmine.createSpyObj('zoom', [
|
||||
'bounds',
|
||||
'toPixels'
|
||||
]);
|
||||
mockScope.scroll = { x: 10, width: 1000 };
|
||||
|
||||
spyOn(mockmct.time, "on").andCallThrough();
|
||||
spyOn(mockmct.time, "off").andCallThrough();
|
||||
spyOn(mockTimerService, "on").andCallThrough();
|
||||
spyOn(mockTimerService, "off").andCallThrough();
|
||||
|
||||
controller = new TimelineTOIController(
|
||||
mockmct,
|
||||
mockTimerService,
|
||||
mockScope
|
||||
);
|
||||
});
|
||||
|
||||
it("reports an undefined x position initially", function () {
|
||||
expect(controller.x()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("listens for bounds changes", function () {
|
||||
expect(mockmct.time.on)
|
||||
.toHaveBeenCalledWith('bounds', controller.bounds);
|
||||
});
|
||||
|
||||
it("listens for timer changes", function () {
|
||||
expect(mockTimerService.on)
|
||||
.toHaveBeenCalledWith('change', controller.change);
|
||||
});
|
||||
|
||||
it("is not active", function () {
|
||||
expect(controller.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
describe("on $destroy from scope", function () {
|
||||
beforeEach(function () {
|
||||
mockScope.emit("$destroy");
|
||||
});
|
||||
|
||||
it("unregisters listeners", function () {
|
||||
expect(mockmct.time.off)
|
||||
.toHaveBeenCalledWith('bounds', controller.bounds);
|
||||
expect(mockTimerService.off)
|
||||
.toHaveBeenCalledWith('change', controller.change);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a timer and timestamp present", function () {
|
||||
var mockTimer;
|
||||
var testNow;
|
||||
|
||||
beforeEach(function () {
|
||||
testNow = 333221;
|
||||
mockScope.zoomController.toPixels
|
||||
.andCallFake(function (millis) {
|
||||
return millis * 2;
|
||||
});
|
||||
mockTimerService.emit('change', mockTimer);
|
||||
mockTimerService.now.andReturn(testNow);
|
||||
});
|
||||
|
||||
it("reports an x value from the zoomController", function () {
|
||||
var now = mockTimerService.now();
|
||||
var expected = mockScope.zoomController.toPixels(now);
|
||||
expect(controller.x()).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when follow mode is disabled", function () {
|
||||
beforeEach(function () {
|
||||
mockScope.scroll.follow = false;
|
||||
});
|
||||
|
||||
it("ignores bounds events", function () {
|
||||
mockmct.time.emit('bounds', { start: 0, end: 1000 });
|
||||
expect(mockScope.zoomController.bounds)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when follow mode is enabled", function () {
|
||||
beforeEach(function () {
|
||||
mockScope.scroll.follow = true;
|
||||
mockTimerService.now.andReturn(500);
|
||||
});
|
||||
|
||||
it("zooms on bounds events", function () {
|
||||
mockmct.time.emit('bounds', { start: 0, end: 1000 });
|
||||
expect(mockScope.zoomController.bounds)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,159 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../src/directives/MCTResourceGraphDrop', '../../src/directives/SwimlaneDragConstants'],
|
||||
function (MCTResourceGraphDrop, SwimlaneDragConstants) {
|
||||
|
||||
describe("The mct-resource-graph-drop directive", function () {
|
||||
var mockDndService,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockSwimlane,
|
||||
testEvent,
|
||||
handlers,
|
||||
directive;
|
||||
|
||||
beforeEach(function () {
|
||||
handlers = {};
|
||||
|
||||
mockDndService = jasmine.createSpyObj(
|
||||
'dndService',
|
||||
['setData', 'getData', 'removeData']
|
||||
);
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$eval', '$apply']);
|
||||
mockElement = jasmine.createSpyObj('element', ['on', 'addClass', 'removeClass']);
|
||||
testAttrs = { mctSwimlaneDrop: "mockSwimlane" };
|
||||
mockSwimlane = jasmine.createSpyObj(
|
||||
"swimlane",
|
||||
['graph', 'toggleGraph']
|
||||
);
|
||||
|
||||
testEvent = {
|
||||
dataTransfer: { getData: jasmine.createSpy() },
|
||||
preventDefault: jasmine.createSpy(),
|
||||
stopPropagation: jasmine.createSpy()
|
||||
};
|
||||
|
||||
testEvent.dataTransfer.getData.andReturn('abc');
|
||||
mockDndService.getData.andCallFake(function (key) {
|
||||
return key === SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE ?
|
||||
mockSwimlane : undefined;
|
||||
});
|
||||
|
||||
mockSwimlane.graph.andReturn(false);
|
||||
|
||||
directive = new MCTResourceGraphDrop(mockDndService);
|
||||
directive.link(mockScope, mockElement, testAttrs);
|
||||
|
||||
mockElement.on.calls.forEach(function (call) {
|
||||
handlers[call.args[0]] = call.args[1];
|
||||
});
|
||||
});
|
||||
|
||||
it("is available as an attribute", function () {
|
||||
expect(directive.restrict).toEqual("A");
|
||||
});
|
||||
|
||||
[false, true].forEach(function (graphing) {
|
||||
describe("when swimlane graph is " + (graphing ? "" : "not ") + "enabled", function () {
|
||||
beforeEach(function () {
|
||||
mockSwimlane.graph.andReturn(graphing);
|
||||
});
|
||||
|
||||
|
||||
describe("on dragover", function () {
|
||||
var prefix = !graphing ? "does" : "does not";
|
||||
|
||||
beforeEach(function () {
|
||||
handlers.dragover(testEvent);
|
||||
});
|
||||
|
||||
it(prefix + " add a drop-over class", function () {
|
||||
var expectAddClass = expect(mockElement.addClass);
|
||||
(!graphing ? expectAddClass : expectAddClass.not)
|
||||
.toHaveBeenCalledWith('drop-over');
|
||||
});
|
||||
|
||||
it(prefix + " call $apply on scope", function () {
|
||||
var expectApply = expect(mockScope.$apply);
|
||||
(!graphing ? expectApply : expectApply.not)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(prefix + " prevent default", function () {
|
||||
var expectPreventDefault = expect(testEvent.preventDefault);
|
||||
(!graphing ? expectPreventDefault : expectPreventDefault.not)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("on drop", function () {
|
||||
var prefix = !graphing ? "does" : "does not";
|
||||
|
||||
beforeEach(function () {
|
||||
handlers.drop(testEvent);
|
||||
});
|
||||
|
||||
it("removes any drop-over class", function () {
|
||||
expect(mockElement.removeClass)
|
||||
.toHaveBeenCalledWith('drop-over');
|
||||
});
|
||||
|
||||
it(prefix + " toggle the swimlane's resource graph", function () {
|
||||
var expectToggle = expect(mockSwimlane.toggleGraph);
|
||||
(!graphing ? expectToggle : expectToggle.not)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(prefix + " prevent default", function () {
|
||||
var expectPreventDefault = expect(testEvent.preventDefault);
|
||||
(!graphing ? expectPreventDefault : expectPreventDefault.not)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("on dragleave", function () {
|
||||
beforeEach(function () {
|
||||
handlers.dragleave(testEvent);
|
||||
});
|
||||
|
||||
it("removes any drop-over class", function () {
|
||||
expect(mockElement.removeClass)
|
||||
.toHaveBeenCalledWith('drop-over');
|
||||
});
|
||||
|
||||
it("calls $apply on scope", function () {
|
||||
expect(mockScope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls preventDefault on events", function () {
|
||||
expect(testEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -27,7 +27,7 @@
|
||||
ng-required="ngRequired || compositeCtrl.isNonEmpty(ngModel[field])"
|
||||
ng-pattern="ngPattern"
|
||||
options="item.options"
|
||||
structure="row"
|
||||
structure="item"
|
||||
field="$index">
|
||||
</mct-control>
|
||||
<span class="composite-control-label">
|
||||
|
@ -50,11 +50,18 @@ define([
|
||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fallback provider, this is an internal API for legacy reasons.
|
||||
* @private
|
||||
*/
|
||||
ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
||||
this.fallbackProvider = p;
|
||||
};
|
||||
|
||||
// Retrieve the provider for a given key.
|
||||
/**
|
||||
* Retrieve the provider for a given identifier.
|
||||
* @private
|
||||
*/
|
||||
ObjectAPI.prototype.getProvider = function (identifier) {
|
||||
if (identifier.key === 'ROOT') {
|
||||
return this.rootProvider;
|
||||
@ -135,27 +142,28 @@ define([
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
ObjectAPI.prototype.get = function (identifier) {
|
||||
identifier = utils.parseKeyString(identifier);
|
||||
var provider = this.getProvider(identifier);
|
||||
|
||||
[
|
||||
'save',
|
||||
'delete',
|
||||
'get'
|
||||
].forEach(function (method) {
|
||||
ObjectAPI.prototype[method] = function () {
|
||||
var identifier = arguments[0],
|
||||
provider = this.getProvider(identifier);
|
||||
if (!provider) {
|
||||
throw new Error('No Provider Matched');
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
throw new Error('No Provider Matched');
|
||||
}
|
||||
if (!provider.get) {
|
||||
throw new Error('Provider does not support get!');
|
||||
}
|
||||
|
||||
if (!provider[method]) {
|
||||
throw new Error('Provider does not support [' + method + '].');
|
||||
}
|
||||
return provider.get(identifier);
|
||||
};
|
||||
|
||||
return provider[method].apply(provider, arguments);
|
||||
};
|
||||
});
|
||||
ObjectAPI.prototype.delete = function () {
|
||||
throw new Error('Delete not implemented');
|
||||
};
|
||||
|
||||
ObjectAPI.prototype.save = function () {
|
||||
throw new Error('Save not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a root-level object.
|
||||
|
@ -27,7 +27,8 @@ define([
|
||||
'../../platform/features/autoflow/plugin',
|
||||
'./timeConductor/plugin',
|
||||
'../../example/imagery/plugin',
|
||||
'../../platform/import-export/bundle'
|
||||
'../../platform/import-export/bundle',
|
||||
'./telemetryMean/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -35,7 +36,8 @@ define([
|
||||
AutoflowPlugin,
|
||||
TimeConductorPlugin,
|
||||
ExampleImagery,
|
||||
ImportExport
|
||||
ImportExport,
|
||||
TelemetryMean
|
||||
) {
|
||||
var bundleMap = {
|
||||
CouchDB: 'platform/persistence/couch',
|
||||
@ -120,6 +122,7 @@ define([
|
||||
};
|
||||
|
||||
plugins.ExampleImagery = ExampleImagery;
|
||||
plugins.TelemetryMean = TelemetryMean;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
76
src/plugins/telemetryMean/plugin.js
Executable file
76
src/plugins/telemetryMean/plugin.js
Executable file
@ -0,0 +1,76 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./src/MeanTelemetryProvider'], function (MeanTelemetryProvider) {
|
||||
var DEFAULT_SAMPLES = 10;
|
||||
|
||||
function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('telemetry-mean', {
|
||||
name: 'Telemetry Mean',
|
||||
description: 'Provides telemetry values that represent the mean of the last N values of a telemetry stream',
|
||||
creatable: true,
|
||||
cssClass: 'icon-telemetry',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.samples = DEFAULT_SAMPLES;
|
||||
domainObject.telemetry = {};
|
||||
domainObject.telemetry.values =
|
||||
openmct.time.getAllTimeSystems().map(function (timeSystem, index) {
|
||||
return {
|
||||
key: timeSystem.key,
|
||||
name: timeSystem.name,
|
||||
hints: {
|
||||
domain: index + 1
|
||||
}
|
||||
};
|
||||
});
|
||||
domainObject.telemetry.values.push({
|
||||
key: "value",
|
||||
name: "Value",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
});
|
||||
},
|
||||
form: [
|
||||
{
|
||||
"key": "telemetryPoint",
|
||||
"name": "Telemetry Point",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
{
|
||||
"key": "samples",
|
||||
"name": "Samples to Average",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-sm"
|
||||
}
|
||||
]
|
||||
});
|
||||
openmct.telemetry.addProvider(new MeanTelemetryProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
return plugin;
|
||||
});
|
125
src/plugins/telemetryMean/src/MeanTelemetryProvider.js
Normal file
125
src/plugins/telemetryMean/src/MeanTelemetryProvider.js
Normal file
@ -0,0 +1,125 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
/*jshint latedef: nofunc */
|
||||
/*global console */
|
||||
define([
|
||||
'../../../api/objects/object-utils',
|
||||
'./TelemetryAverager'
|
||||
], function (objectUtils, TelemetryAverager) {
|
||||
|
||||
function MeanTelemetryProvider(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.telemetryAPI = openmct.telemetry;
|
||||
this.timeAPI = openmct.time;
|
||||
this.objectAPI = openmct.objects;
|
||||
this.perObjectProviders = {};
|
||||
}
|
||||
|
||||
MeanTelemetryProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'telemetry-mean';
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.supportsRequest =
|
||||
MeanTelemetryProvider.prototype.supportsSubscribe =
|
||||
MeanTelemetryProvider.prototype.canProvideTelemetry;
|
||||
|
||||
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var wrappedUnsubscribe;
|
||||
var unsubscribeCalled = false;
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
var samples = domainObject.samples;
|
||||
|
||||
this.objectAPI.get(objectId)
|
||||
.then(function (linkedDomainObject) {
|
||||
if (!unsubscribeCalled) {
|
||||
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(logError);
|
||||
|
||||
return function unsubscribe() {
|
||||
unsubscribeCalled = true;
|
||||
if (wrappedUnsubscribe !== undefined) {
|
||||
wrappedUnsubscribe();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, samples, callback) {
|
||||
var telemetryAverager = new TelemetryAverager(this.telemetryAPI, this.timeAPI, domainObject, samples);
|
||||
|
||||
return this.telemetryAPI.subscribe(domainObject, function (telemetryDatum) {
|
||||
var avgData = telemetryAverager.createAverageDatum(telemetryDatum);
|
||||
|
||||
if (telemetryAverager.sampleCount() === samples) {
|
||||
callback(avgData);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
var samples = domainObject.samples;
|
||||
|
||||
return this.objectAPI.get(objectId).then(function (linkedDomainObject) {
|
||||
return this.requestAverageTelemetry(linkedDomainObject, request, samples);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.requestAverageTelemetry = function (domainObject, request, samples) {
|
||||
var averageData = [];
|
||||
var telemetryAverager = new TelemetryAverager(this.telemetryAPI, this.timeAPI, domainObject, samples);
|
||||
|
||||
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData) {
|
||||
telemetryData.forEach(function (datum) {
|
||||
var avgDatum = telemetryAverager.createAverageDatum(datum);
|
||||
|
||||
if (telemetryAverager.sampleCount() === samples) {
|
||||
averageData.push(avgDatum);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return averageData;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
return this.objectAPI.get(objectId);
|
||||
};
|
||||
|
||||
function logError(error) {
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return MeanTelemetryProvider;
|
||||
});
|
460
src/plugins/telemetryMean/src/MeanTelemetryProviderSpec.js
Normal file
460
src/plugins/telemetryMean/src/MeanTelemetryProviderSpec.js
Normal file
@ -0,0 +1,460 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
/*jshint latedef: nofunc */
|
||||
define([
|
||||
"./MeanTelemetryProvider",
|
||||
"./MockTelemetryApi"
|
||||
], function (
|
||||
MeanTelemetryProvider,
|
||||
MockTelemetryApi
|
||||
) {
|
||||
var RANGE_KEY = 'value';
|
||||
|
||||
describe("The Mean Telemetry Provider", function () {
|
||||
var mockApi;
|
||||
var meanTelemetryProvider;
|
||||
var outstandingPromises = 0;
|
||||
var mockDomainObject;
|
||||
var associatedObject;
|
||||
|
||||
beforeEach(function () {
|
||||
createMockApi();
|
||||
setTimeSystemTo('utc');
|
||||
createMockObjects();
|
||||
meanTelemetryProvider = new MeanTelemetryProvider(mockApi);
|
||||
});
|
||||
|
||||
it("supports telemetry-mean objects only", function () {
|
||||
var mockTelemetryMeanObject = mockObjectWithType('telemetry-mean');
|
||||
var mockOtherObject = mockObjectWithType('other');
|
||||
|
||||
expect(meanTelemetryProvider.canProvideTelemetry(mockTelemetryMeanObject)).toBe(true);
|
||||
expect(meanTelemetryProvider.canProvideTelemetry(mockOtherObject)).toBe(false);
|
||||
});
|
||||
|
||||
describe("the subscribe function", function () {
|
||||
var subscriptionCallback;
|
||||
|
||||
beforeEach(function () {
|
||||
subscriptionCallback = jasmine.createSpy('subscriptionCallback');
|
||||
});
|
||||
|
||||
it("subscribes to telemetry for the associated object", function () {
|
||||
meanTelemetryProvider.subscribe(mockDomainObject);
|
||||
|
||||
expectObjectWasSubscribedTo(associatedObject);
|
||||
});
|
||||
|
||||
it("returns a function that unsubscribes from the associated object", function () {
|
||||
var unsubscribe = meanTelemetryProvider.subscribe(mockDomainObject);
|
||||
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(unsubscribe);
|
||||
|
||||
expectUnsubscribeFrom(associatedObject);
|
||||
});
|
||||
|
||||
it("returns an average only when the sample size is reached", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313}
|
||||
];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectNoAverageForTelemetry(inputTelemetry);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of five values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231}
|
||||
];
|
||||
var expectedAverages = [{
|
||||
'utc': 5, 'value': 222.44888
|
||||
}];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of ten values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var expectedAverages = [{
|
||||
'utc': 10, 'value': 451.07815
|
||||
}];
|
||||
|
||||
setSampleSize(10);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
|
||||
it("only averages values within its sample window", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var expectedAverages = [
|
||||
{'utc': 5, 'value': 222.44888},
|
||||
{'utc': 6, 'value': 662.4482599999999},
|
||||
{'utc': 7, 'value': 704.6078},
|
||||
{'utc': 8, 'value': 773.02748},
|
||||
{'utc': 9, 'value': 679.8234399999999},
|
||||
{'utc': 10, 'value': 679.70742}
|
||||
];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
describe("given telemetry input with range values", function () {
|
||||
var inputTelemetry;
|
||||
|
||||
beforeEach(function () {
|
||||
inputTelemetry = [{
|
||||
'utc': 1,
|
||||
'rangeKey': 5678,
|
||||
'otherKey': 9999
|
||||
}];
|
||||
setSampleSize(1);
|
||||
});
|
||||
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForRangeKey = [{
|
||||
'utc': 1,
|
||||
'value': 5678
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('rangeKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
|
||||
});
|
||||
|
||||
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForOtherKey = [{
|
||||
'utc': 1,
|
||||
'value': 9999
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('otherKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
|
||||
|
||||
});
|
||||
});
|
||||
describe("given telemetry input with range values", function () {
|
||||
var inputTelemetry;
|
||||
|
||||
beforeEach(function () {
|
||||
inputTelemetry = [{
|
||||
'utc': 1,
|
||||
'rangeKey': 5678,
|
||||
'otherKey': 9999
|
||||
}];
|
||||
setSampleSize(1);
|
||||
});
|
||||
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForRangeKey = [{
|
||||
'utc': 1,
|
||||
'value': 5678
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('rangeKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
|
||||
});
|
||||
|
||||
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForOtherKey = [{
|
||||
'utc': 1,
|
||||
'value': 9999
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('otherKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
|
||||
});
|
||||
});
|
||||
|
||||
function feedInputTelemetry(inputTelemetry) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
inputTelemetry.forEach(mockApi.telemetry.mockReceiveTelemetry);
|
||||
});
|
||||
}
|
||||
|
||||
function expectNoAverageForTelemetry(inputTelemetry) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(subscriptionCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
function expectAveragesForTelemetry(expectedAverages) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expectedAverages.forEach(function (averageDatum) {
|
||||
expect(subscriptionCallback).toHaveBeenCalledWith(averageDatum);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function expectObjectWasSubscribedTo(object) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.subscribe).toHaveBeenCalledWith(object, jasmine.any(Function));
|
||||
});
|
||||
}
|
||||
|
||||
function expectUnsubscribeFrom() {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
describe("the request function", function () {
|
||||
|
||||
it("requests telemetry for the associated object", function () {
|
||||
meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectTelemetryToBeRequestedFor(associatedObject);
|
||||
});
|
||||
|
||||
it("returns an average only when the sample size is reached", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectEmptyResponse(promiseForAverage);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of five values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(222.44888, promiseForAverage);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of ten values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(10);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(451.07815, promiseForAverage);
|
||||
});
|
||||
|
||||
it("only averages values within its sample window", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(679.70742, promiseForAverage);
|
||||
});
|
||||
|
||||
function expectAverageToBe(expectedValue, promiseForAverage) {
|
||||
var averageData;
|
||||
|
||||
promiseForAverage.then(function (data) {
|
||||
averageData = data;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return averageData !== undefined;
|
||||
}, 'data to return from request', 1);
|
||||
runs(function () {
|
||||
var averageDatum = averageData[averageData.length - 1];
|
||||
expect(averageDatum[RANGE_KEY]).toBe(expectedValue);
|
||||
});
|
||||
}
|
||||
|
||||
function expectEmptyResponse(promiseForAverage) {
|
||||
var averageData;
|
||||
|
||||
promiseForAverage.then(function (data) {
|
||||
averageData = data;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return averageData !== undefined;
|
||||
}, 'data to return from request', 1);
|
||||
runs(function () {
|
||||
expect(averageData.length).toBe(0);
|
||||
});
|
||||
}
|
||||
|
||||
function whenTelemetryRequestedReturn(telemetry) {
|
||||
mockApi.telemetry.request.andReturn(resolvePromiseWith(telemetry));
|
||||
}
|
||||
|
||||
function expectTelemetryToBeRequestedFor(object) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.request).toHaveBeenCalledWith(object, undefined);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function createMockObjects() {
|
||||
mockDomainObject = {
|
||||
telemetryPoint: 'someTelemetryPoint'
|
||||
};
|
||||
associatedObject = {};
|
||||
mockApi.objects.get.andReturn(resolvePromiseWith(associatedObject));
|
||||
}
|
||||
|
||||
function setSampleSize(sampleSize) {
|
||||
mockDomainObject.samples = sampleSize;
|
||||
}
|
||||
|
||||
function createMockApi() {
|
||||
mockApi = {
|
||||
telemetry: new MockTelemetryApi(),
|
||||
objects: createMockObjectApi(),
|
||||
time: createMockTimeApi()
|
||||
};
|
||||
}
|
||||
|
||||
function createMockObjectApi() {
|
||||
return jasmine.createSpyObj('ObjectAPI', [
|
||||
'get'
|
||||
]);
|
||||
}
|
||||
|
||||
function mockObjectWithType(type) {
|
||||
return {
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePromiseWith(value) {
|
||||
outstandingPromises++;
|
||||
return Promise.resolve(value).then(function () {
|
||||
outstandingPromises--;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function allPromisesToBeResolved() {
|
||||
return outstandingPromises === 0;
|
||||
}
|
||||
|
||||
function createMockTimeApi() {
|
||||
return jasmine.createSpyObj("timeApi", ['timeSystem']);
|
||||
}
|
||||
|
||||
function setTimeSystemTo(timeSystemKey) {
|
||||
mockApi.time.timeSystem.andReturn({
|
||||
key: timeSystemKey
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
106
src/plugins/telemetryMean/src/MockTelemetryApi.js
Normal file
106
src/plugins/telemetryMean/src/MockTelemetryApi.js
Normal file
@ -0,0 +1,106 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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 jasmine, spyOn */
|
||||
define([], function () {
|
||||
|
||||
function MockTelemetryApi() {
|
||||
this.createSpy('subscribe');
|
||||
this.createSpy('getMetadata');
|
||||
|
||||
this.metadata = this.createMockMetadata();
|
||||
this.setDefaultRangeTo('defaultRange');
|
||||
this.unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
this.mockReceiveTelemetry = this.mockReceiveTelemetry.bind(this);
|
||||
}
|
||||
|
||||
MockTelemetryApi.prototype.subscribe = function () {
|
||||
return this.unsubscribe;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.getMetadata = function (object) {
|
||||
return this.metadata;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.request = jasmine.createSpy('request');
|
||||
|
||||
MockTelemetryApi.prototype.getValueFormatter = function (valueMetadata) {
|
||||
var mockValueFormatter = jasmine.createSpyObj("valueFormatter", [
|
||||
"parse"
|
||||
]);
|
||||
|
||||
mockValueFormatter.parse.andCallFake(function (value) {
|
||||
return value[valueMetadata.key];
|
||||
});
|
||||
|
||||
return mockValueFormatter;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.mockReceiveTelemetry = function (newTelemetryDatum) {
|
||||
var subscriptionCallback = this.subscribe.mostRecentCall.args[1];
|
||||
subscriptionCallback(newTelemetryDatum);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.onRequestReturn = function (telemetryData) {
|
||||
this.requestTelemetry = telemetryData;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.setDefaultRangeTo = function (rangeKey) {
|
||||
var mockMetadataValue = {
|
||||
key: rangeKey
|
||||
};
|
||||
this.metadata.valuesForHints.andReturn([mockMetadataValue]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.createMockMetadata = function () {
|
||||
var mockMetadata = jasmine.createSpyObj("metadata", [
|
||||
'value',
|
||||
'valuesForHints'
|
||||
]);
|
||||
|
||||
mockMetadata.value.andCallFake(function (key) {
|
||||
return {
|
||||
key: key
|
||||
};
|
||||
});
|
||||
return mockMetadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.createSpy = function (functionName) {
|
||||
this[functionName] = this[functionName].bind(this);
|
||||
spyOn(this, functionName);
|
||||
this[functionName].andCallThrough();
|
||||
};
|
||||
|
||||
return MockTelemetryApi;
|
||||
});
|
112
src/plugins/telemetryMean/src/TelemetryAverager.js
Normal file
112
src/plugins/telemetryMean/src/TelemetryAverager.js
Normal file
@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
function TelemetryAverager(telemetryAPI, timeAPI, domainObject, samples) {
|
||||
this.telemetryAPI = telemetryAPI;
|
||||
this.timeAPI = timeAPI;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.samples = samples;
|
||||
this.averagingWindow = [];
|
||||
|
||||
this.rangeKey = undefined;
|
||||
this.rangeFormatter = undefined;
|
||||
this.setRangeKeyAndFormatter();
|
||||
|
||||
// Defined dynamically based on current time system
|
||||
this.domainKey = undefined;
|
||||
this.domainFormatter = undefined;
|
||||
}
|
||||
|
||||
TelemetryAverager.prototype.createAverageDatum = function (telemetryDatum) {
|
||||
this.setDomainKeyAndFormatter();
|
||||
|
||||
var timeValue = this.domainFormatter.parse(telemetryDatum);
|
||||
var rangeValue = this.rangeFormatter.parse(telemetryDatum);
|
||||
|
||||
this.averagingWindow.push(rangeValue);
|
||||
if (this.averagingWindow.length > this.samples) {
|
||||
this.averagingWindow.shift();
|
||||
}
|
||||
var averageValue = this.calculateMean();
|
||||
|
||||
var meanDatum = {};
|
||||
meanDatum[this.domainKey] = timeValue;
|
||||
meanDatum.value = averageValue;
|
||||
|
||||
return meanDatum;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.calculateMean = function () {
|
||||
var sum = 0;
|
||||
var i = 0;
|
||||
|
||||
for (; i < this.averagingWindow.length; i++) {
|
||||
sum += this.averagingWindow[i];
|
||||
}
|
||||
|
||||
return sum / this.averagingWindow.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
|
||||
var domainKey = this.timeAPI.timeSystem().key;
|
||||
if (domainKey !== this.domainKey) {
|
||||
this.domainKey = domainKey;
|
||||
this.domainFormatter = this.getFormatter(domainKey);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.setRangeKeyAndFormatter = function () {
|
||||
var metadatas = this.telemetryAPI.getMetadata(this.domainObject);
|
||||
var rangeValues = metadatas.valuesForHints(['range']);
|
||||
|
||||
this.rangeKey = rangeValues[0].key;
|
||||
this.rangeFormatter = this.getFormatter(this.rangeKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.getFormatter = function (key) {
|
||||
var objectMetadata = this.telemetryAPI.getMetadata(this.domainObject);
|
||||
var valueMetadata = objectMetadata.value(key);
|
||||
|
||||
return this.telemetryAPI.getValueFormatter(valueMetadata);
|
||||
};
|
||||
|
||||
TelemetryAverager.prototype.sampleCount = function () {
|
||||
return this.averagingWindow.length;
|
||||
};
|
||||
|
||||
return TelemetryAverager;
|
||||
});
|
@ -59,7 +59,7 @@ requirejs.config({
|
||||
"moment": "bower_components/moment/moment",
|
||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
|
||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||
"saveAs": "bower_components/file-saver/FileSaver.min",
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
|
Reference in New Issue
Block a user