Merge remote-tracking branch 'github-open/open115b' into open-master

This commit is contained in:
Pete Richards 2015-10-09 13:50:27 -07:00
commit c703714cb3
15 changed files with 338 additions and 81 deletions

View File

@ -34,6 +34,10 @@
{
"key": "time",
"name": "Time"
},
{
"key": "yesterday",
"name": "Yesterday"
}
],
"ranges": [
@ -61,4 +65,4 @@
}
]
}
}
}

View File

@ -29,23 +29,25 @@ define(
function () {
"use strict";
var firstObservedTime = Math.floor(Date.now() / 1000);
var ONE_DAY = 60 * 60 * 24,
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
/**
*
* @constructor
*/
function SinewaveTelemetrySeries(request) {
var latestObservedTime = Math.floor(Date.now() / 1000),
var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
latestTime = Math.floor(Date.now() / 1000) - timeOffset,
firstTime = firstObservedTime - timeOffset,
endTime = (request.end !== undefined) ?
Math.floor(request.end / 1000) : latestObservedTime,
count =
Math.min(endTime, latestObservedTime) - firstObservedTime,
period = request.period || 30,
Math.floor(request.end / 1000) : latestTime,
count = Math.min(endTime, latestTime) - firstTime,
period = +request.period || 30,
generatorData = {},
offset = (request.start !== undefined) ?
Math.floor(request.start / 1000) - firstObservedTime :
0;
requestStart = (request.start === undefined) ? firstTime :
Math.max(Math.floor(request.start / 1000), firstTime),
offset = requestStart - firstTime;
if (request.size !== undefined) {
offset = Math.max(offset, count - request.size);
@ -56,8 +58,8 @@ define(
};
generatorData.getDomainValue = function (i, domain) {
return (i + offset) * 1000 +
(domain !== 'delta' ? (firstObservedTime * 1000) : 0);
return (i + offset) * 1000 + firstTime * 1000 -
(domain === 'yesterday' ? ONE_DAY : 0);
};
generatorData.getRangeValue = function (i, range) {

View File

@ -23,7 +23,23 @@
{
"key": "conductorService",
"implementation": "ConductorService.js",
"depends": [ "now" ]
"depends": [ "now", "TIME_CONDUCTOR_DOMAINS" ]
}
],
"templates": [
{
"key": "time-conductor",
"templateUrl": "templates/time-conductor.html"
}
],
"constants": [
{
"key": "TIME_CONDUCTOR_DOMAINS",
"value": [
{ "key": "time", "name": "Time" },
{ "key": "yesterday", "name": "Yesterday" }
],
"comment": "Placeholder; to be replaced by inspection of available domains."
}
]
}

View File

@ -0,0 +1,10 @@
<mct-include key="'time-controller'"
ng-model='ngModel.conductor'>
</mct-include>
<mct-control key="'select'"
ng-model='ngModel'
field="'domain'"
options="ngModel.options"
style="position: absolute; right: 0px; bottom: 46px;"
>
</mct-control>

View File

@ -28,7 +28,7 @@ define(
var CONDUCTOR_HEIGHT = "100px",
TEMPLATE = [
"<mct-include key=\"'time-controller'\" ng-model='conductor'>",
"<mct-include key=\"'time-conductor'\" ng-model='ngModel'>",
"</mct-include>"
].join(''),
THROTTLE_MS = 200,
@ -78,7 +78,8 @@ define(
function bounds(start, end) {
return {
start: conductor.displayStart(),
end: conductor.displayEnd()
end: conductor.displayEnd(),
domain: conductor.domain()
};
}
@ -89,12 +90,30 @@ define(
}
function updateConductorInner() {
conductor.displayStart(conductorScope.conductor.inner.start);
conductor.displayEnd(conductorScope.conductor.inner.end);
var innerBounds = conductorScope.ngModel.conductor.inner;
conductor.displayStart(innerBounds.start);
conductor.displayEnd(innerBounds.end);
lastObservedBounds = lastObservedBounds || bounds();
broadcastBounds();
}
function updateDomain(value) {
conductor.domain(value);
repScope.$broadcast('telemetry:display:bounds', bounds(
conductor.displayStart(),
conductor.displayEnd(),
conductor.domain()
));
}
// telemetry domain metadata -> option for a select control
function makeOption(domainOption) {
return {
name: domainOption.name,
value: domainOption.key
};
}
broadcastBounds = this.throttle(function () {
var newlyObservedBounds = bounds();
@ -107,12 +126,19 @@ define(
}
}, THROTTLE_MS);
conductorScope.conductor = { outer: bounds(), inner: bounds() };
conductorScope.ngModel = {};
conductorScope.ngModel.conductor =
{ outer: bounds(), inner: bounds() };
conductorScope.ngModel.options =
conductor.domainOptions().map(makeOption);
conductorScope.ngModel.domain = conductor.domain();
conductorScope
.$watch('conductor.inner.start', updateConductorInner);
.$watch('ngModel.conductor.inner.start', updateConductorInner);
conductorScope
.$watch('conductor.inner.end', updateConductorInner);
.$watch('ngModel.conductor.inner.end', updateConductorInner);
conductorScope
.$watch('ngModel.domain', updateDomain);
repScope.$on('telemetry:view', updateConductorInner);
};

View File

@ -39,12 +39,15 @@ define(
* @param {Function} now a function which returns the current time
* as a UNIX timestamp, in milliseconds
*/
function ConductorService(now) {
function ConductorService(now, domains) {
var initialEnd =
Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS;
this.conductor =
new TimeConductor(initialEnd - ONE_DAY_IN_MS, initialEnd);
this.conductor = new TimeConductor(
initialEnd - ONE_DAY_IN_MS,
initialEnd,
domains
);
}
/**

View File

@ -44,12 +44,14 @@ define(
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
var conductor = this.conductorService.getConductor(),
start = conductor.displayStart(),
end = conductor.displayEnd();
end = conductor.displayEnd(),
domain = conductor.domain();
function amendRequest(request) {
request = request || {};
request.start = start;
request.end = end;
request.domain = domain;
return request;
}

View File

@ -40,8 +40,10 @@ define(
* @param {number} start the initial start time
* @param {number} end the initial end time
*/
function TimeConductor(start, end) {
function TimeConductor(start, end, domains) {
this.range = { start: start, end: end };
this.domains = domains;
this.activeDomain = domains[0].key;
}
/**
@ -68,6 +70,34 @@ define(
return this.range.end;
};
/**
* Get available domain options which can be used to bound time
* selection.
* @returns {TelemetryDomain[]} available domains
*/
TimeConductor.prototype.domainOptions = function () {
return this.domains;
};
/**
* Get or set (if called with an argument) the active domain.
* @param {string} [key] the key identifying the domain choice
* @returns {TelemetryDomain} the active telemetry domain
*/
TimeConductor.prototype.domain = function (key) {
function matchesKey(domain) {
return domain.key === key;
}
if (arguments.length > 0) {
if (!this.domains.some(matchesKey)) {
throw new Error("Unknown domain " + key);
}
this.activeDomain = key;
}
return this.activeDomain;
};
return TimeConductor;
}
);

View File

@ -21,12 +21,9 @@
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,afterEach,jasmine*/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/ConductorRepresenter"],
function (ConductorRepresenter) {
["../src/ConductorRepresenter", "./TestTimeConductor"],
function (ConductorRepresenter, TestTimeConductor) {
"use strict";
var SCOPE_METHODS = [
@ -77,10 +74,7 @@ define(
testViews = [ { someKey: "some value" } ];
mockScope = jasmine.createSpyObj('scope', SCOPE_METHODS);
mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS);
mockConductor = jasmine.createSpyObj(
'conductor',
[ 'displayStart', 'displayEnd' ]
);
mockConductor = new TestTimeConductor();
mockCompiledTemplate = jasmine.createSpy('template');
mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS);
mockNewElement = jasmine.createSpyObj('newElement', ELEMENT_METHODS);
@ -135,11 +129,12 @@ define(
it("exposes conductor state in scope", function () {
mockConductor.displayStart.andReturn(1977);
mockConductor.displayEnd.andReturn(1984);
mockConductor.domain.andReturn('d');
representer.represent(testViews[0], {});
expect(mockNewScope.conductor).toEqual({
inner: { start: 1977, end: 1984 },
outer: { start: 1977, end: 1984 }
expect(mockNewScope.ngModel.conductor).toEqual({
inner: { start: 1977, end: 1984, domain: 'd' },
outer: { start: 1977, end: 1984, domain: 'd' }
});
});
@ -151,17 +146,27 @@ define(
representer.represent(testViews[0], {});
mockNewScope.conductor = testState;
mockNewScope.ngModel.conductor = testState;
fireWatch(mockNewScope, 'conductor.inner.start', testState.inner.start);
fireWatch(
mockNewScope,
'ngModel.conductor.inner.start',
testState.inner.start
);
expect(mockConductor.displayStart).toHaveBeenCalledWith(42);
fireWatch(mockNewScope, 'conductor.inner.end', testState.inner.end);
fireWatch(
mockNewScope,
'ngModel.conductor.inner.end',
testState.inner.end
);
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
});
describe("when bounds are changing", function () {
var mockThrottledFn = jasmine.createSpy('throttledFn'),
var startWatch = "ngModel.conductor.inner.start",
endWatch = "ngModel.conductor.inner.end",
mockThrottledFn = jasmine.createSpy('throttledFn'),
testBounds;
function fireThrottledFn() {
@ -172,7 +177,7 @@ define(
mockThrottle.andReturn(mockThrottledFn);
representer.represent(testViews[0], {});
testBounds = { start: 0, end: 1000 };
mockNewScope.conductor.inner = testBounds;
mockNewScope.ngModel.conductor.inner = testBounds;
mockConductor.displayStart.andCallFake(function () {
return testBounds.start;
});
@ -184,14 +189,14 @@ define(
it("does not broadcast while bounds are changing", function () {
expect(mockScope.$broadcast).not.toHaveBeenCalled();
testBounds.start = 100;
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
fireWatch(mockNewScope, startWatch, testBounds.start);
testBounds.end = 500;
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
fireWatch(mockNewScope, endWatch, testBounds.end);
fireThrottledFn();
testBounds.start = 200;
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
fireWatch(mockNewScope, startWatch, testBounds.start);
testBounds.end = 400;
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
fireWatch(mockNewScope, endWatch, testBounds.end);
fireThrottledFn();
expect(mockScope.$broadcast).not.toHaveBeenCalled();
});
@ -199,17 +204,56 @@ define(
it("does broadcast when bounds have stabilized", function () {
expect(mockScope.$broadcast).not.toHaveBeenCalled();
testBounds.start = 100;
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
fireWatch(mockNewScope, startWatch, testBounds.start);
testBounds.end = 500;
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
fireWatch(mockNewScope, endWatch, testBounds.end);
fireThrottledFn();
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
fireWatch(mockNewScope, startWatch, testBounds.start);
fireWatch(mockNewScope, endWatch, testBounds.end);
fireThrottledFn();
expect(mockScope.$broadcast).toHaveBeenCalled();
});
});
it("exposes domain selection in scope", function () {
representer.represent(testViews[0], null);
expect(mockNewScope.ngModel.domain)
.toEqual(mockConductor.domain());
});
it("exposes domain options in scope", function () {
representer.represent(testViews[0], null);
mockConductor.domainOptions().forEach(function (option, i) {
expect(mockNewScope.ngModel.options[i].value)
.toEqual(option.key);
expect(mockNewScope.ngModel.options[i].name)
.toEqual(option.name);
});
});
it("updates domain selection from scope", function () {
var choice;
representer.represent(testViews[0], null);
// Choose a domain that isn't currently selected
mockNewScope.ngModel.options.forEach(function (option) {
if (option.value !== mockNewScope.ngModel.domain) {
choice = option.value;
}
});
expect(mockConductor.domain)
.not.toHaveBeenCalledWith(choice);
mockNewScope.ngModel.domain = choice;
fireWatch(mockNewScope, "ngModel.domain", choice);
expect(mockConductor.domain)
.toHaveBeenCalledWith(choice);
});
});
}
);

View File

@ -21,9 +21,6 @@
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/ConductorService"],
function (ConductorService) {
@ -38,7 +35,10 @@ define(
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockNow.andReturn(TEST_NOW);
conductorService = new ConductorService(mockNow);
conductorService = new ConductorService(mockNow, [
{ key: "d1", name: "Domain #1" },
{ key: "d2", name: "Domain #2" }
]);
});
it("initializes a time conductor around the current time", function () {

View File

@ -23,8 +23,8 @@
define(
["../src/ConductorTelemetryDecorator"],
function (ConductorTelemetryDecorator) {
["../src/ConductorTelemetryDecorator", "./TestTimeConductor"],
function (ConductorTelemetryDecorator, TestTimeConductor) {
"use strict";
describe("ConductorTelemetryDecorator", function () {
@ -54,10 +54,7 @@ define(
'conductorService',
['getConductor']
);
mockConductor = jasmine.createSpyObj(
'conductor',
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
);
mockConductor = new TestTimeConductor();
mockPromise = jasmine.createSpyObj(
'promise',
['then']
@ -78,10 +75,9 @@ define(
return j * j * j;
});
mockConductor.queryStart.andReturn(-12321);
mockConductor.queryEnd.andReturn(-12321);
mockConductor.displayStart.andReturn(42);
mockConductor.displayEnd.andReturn(1977);
mockConductor.domain.andReturn("testDomain");
decorator = new ConductorTelemetryDecorator(
mockConductorService,
@ -89,24 +85,72 @@ define(
);
});
it("adds display start/end times to historical requests", function () {
describe("decorates historical requests", function () {
var request;
beforeEach(function () {
decorator.requestTelemetry([{ someKey: "some value" }]);
request = mockTelemetryService.requestTelemetry
.mostRecentCall.args[0][0];
});
it("with start times", function () {
expect(request.start).toEqual(mockConductor.displayStart());
});
it("with end times", function () {
expect(request.end).toEqual(mockConductor.displayEnd());
});
it("with domain selection", function () {
expect(request.domain).toEqual(mockConductor.domain());
});
});
describe("decorates subscription requests", function () {
var request;
beforeEach(function () {
var mockCallback = jasmine.createSpy('callback');
decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
request = mockTelemetryService.subscribe
.mostRecentCall.args[1][0];
});
it("with start times", function () {
expect(request.start).toEqual(mockConductor.displayStart());
});
it("with end times", function () {
expect(request.end).toEqual(mockConductor.displayEnd());
});
it("with domain selection", function () {
expect(request.domain).toEqual(mockConductor.domain());
});
});
it("adds display start/end times & domain selection to historical requests", function () {
decorator.requestTelemetry([{ someKey: "some value" }]);
expect(mockTelemetryService.requestTelemetry)
.toHaveBeenCalledWith([{
someKey: "some value",
start: mockConductor.displayStart(),
end: mockConductor.displayEnd()
end: mockConductor.displayEnd(),
domain: jasmine.any(String)
}]);
});
it("adds display start/end times to subscription requests", function () {
it("adds display start/end times & domain selection to subscription requests", function () {
var mockCallback = jasmine.createSpy('callback');
decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
expect(mockTelemetryService.subscribe)
.toHaveBeenCalledWith(jasmine.any(Function), [{
someKey: "some value",
start: mockConductor.displayStart(),
end: mockConductor.displayEnd()
end: mockConductor.displayEnd(),
domain: jasmine.any(String)
}]);
});

View File

@ -0,0 +1,50 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,spyOn*/
define(
["../src/TimeConductor"],
function (TimeConductor) {
'use strict';
function TestTimeConductor() {
var self = this;
TimeConductor.apply(this, [
402514200000,
444546000000,
[
{ key: "domain0", name: "Domain #1" },
{ key: "domain1", name: "Domain #2" }
]
]);
Object.keys(TimeConductor.prototype).forEach(function (method) {
spyOn(self, method).andCallThrough();
});
}
TestTimeConductor.prototype = TimeConductor.prototype;
return TestTimeConductor;
}
);

View File

@ -21,9 +21,6 @@
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/TimeConductor"],
function (TimeConductor) {
@ -32,12 +29,17 @@ define(
describe("TimeConductor", function () {
var testStart,
testEnd,
testDomains,
conductor;
beforeEach(function () {
testStart = 42;
testEnd = 12321;
conductor = new TimeConductor(testStart, testEnd);
testDomains = [
{ key: "d1", name: "Domain #1" },
{ key: "d2", name: "Domain #2" }
];
conductor = new TimeConductor(testStart, testEnd, testDomains);
});
it("provides accessors for query/display start/end times", function () {
@ -52,6 +54,25 @@ define(
expect(conductor.displayEnd()).toEqual(4);
});
it("exposes domain options", function () {
expect(conductor.domainOptions()).toEqual(testDomains);
});
it("exposes the current domain choice", function () {
expect(conductor.domain()).toEqual(testDomains[0].key);
});
it("allows the domain choice to be changed", function () {
conductor.domain(testDomains[1].key);
expect(conductor.domain()).toEqual(testDomains[1].key);
});
it("throws an error on attempts to set an invalid domain", function () {
expect(function () {
conductor.domain("invalid-domain");
}).toThrow();
});
});
}
);

View File

@ -105,11 +105,13 @@ define(
index
);
setDisplayedValue(
telemetryObject,
telemetrySeries.getRangeValue(index),
limit && datum && limit.evaluate(datum)
);
if (index >= 0) {
setDisplayedValue(
telemetryObject,
telemetrySeries.getRangeValue(index),
limit && datum && limit.evaluate(datum)
);
}
}
// Update the displayed value for this object

View File

@ -110,20 +110,23 @@ define(
* Get the latest telemetry datum for this domain object. This
* will be from real-time telemetry, unless an index is specified,
* in which case it will be pulled from the historical telemetry
* series at the specified index.
* series at the specified index. If there is no latest available
* datum, this will return undefined.
*
* @param {DomainObject} domainObject the object of interest
* @param {number} [index] the index of the data of interest
* @returns {TelemetryDatum} the most recent datum
*/
self.getDatum = function (telemetryObject, index) {
function makeNewDatum(series) {
return series ?
subscription.makeDatum(telemetryObject, series, index) :
undefined;
}
return typeof index !== 'number' ?
subscription.getDatum(telemetryObject) :
subscription.makeDatum(
telemetryObject,
this.getSeries(telemetryObject),
index
);
makeNewDatum(this.getSeries(telemetryObject));
};
return self;