mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 01:42:31 +00:00
Merge branch 'open182' into open229
This commit is contained in:
commit
5706fa4567
@ -6,6 +6,7 @@
|
||||
"platform/commonUI/browse",
|
||||
"platform/commonUI/edit",
|
||||
"platform/commonUI/dialog",
|
||||
"platform/commonUI/formats",
|
||||
"platform/commonUI/general",
|
||||
"platform/commonUI/inspect",
|
||||
"platform/commonUI/mobile",
|
||||
|
@ -16,6 +16,23 @@
|
||||
"implementation": "SinewaveLimitCapability.js"
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "example.delta",
|
||||
"implementation": "SinewaveDeltaFormat.js"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "TIME_CONDUCTOR_DOMAINS",
|
||||
"value": [
|
||||
{ "key": "time", "name": "Time" },
|
||||
{ "key": "yesterday", "name": "Yesterday" },
|
||||
{ "key": "delta", "name": "Delta", "format": "example.delta" }
|
||||
],
|
||||
"priority": -1
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"key": "generator",
|
||||
@ -38,6 +55,11 @@
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday"
|
||||
},
|
||||
{
|
||||
"key": "delta",
|
||||
"name": "Delta",
|
||||
"format": "example.delta"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
|
26
example/generator/src/SinewaveConstants.js
Normal file
26
example/generator/src/SinewaveConstants.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise*/
|
||||
|
||||
define({
|
||||
START_TIME: Date.now() - 24 * 60 * 60 * 1000 // Now minus a day.
|
||||
});
|
68
example/generator/src/SinewaveDeltaFormat.js
Normal file
68
example/generator/src/SinewaveDeltaFormat.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise*/
|
||||
|
||||
define(
|
||||
['./SinewaveConstants', 'moment'],
|
||||
function (SinewaveConstants, moment) {
|
||||
"use strict";
|
||||
|
||||
var START_TIME = SinewaveConstants.START_TIME,
|
||||
FORMAT_REGEX = /^-?\d+:\d+:\d+$/,
|
||||
SECOND = 1000,
|
||||
MINUTE = SECOND * 60,
|
||||
HOUR = MINUTE * 60;
|
||||
|
||||
function SinewaveDeltaFormat() {
|
||||
}
|
||||
|
||||
function twoDigit(v) {
|
||||
return v >= 10 ? String(v) : ('0' + v);
|
||||
}
|
||||
|
||||
SinewaveDeltaFormat.prototype.format = function (value) {
|
||||
var delta = Math.abs(value - START_TIME),
|
||||
negative = value < START_TIME,
|
||||
seconds = Math.floor(delta / SECOND) % 60,
|
||||
minutes = Math.floor(delta / MINUTE) % 60,
|
||||
hours = Math.floor(delta / HOUR);
|
||||
return (negative ? "-" : "") +
|
||||
[ hours, minutes, seconds ].map(twoDigit).join(":");
|
||||
};
|
||||
|
||||
SinewaveDeltaFormat.prototype.validate = function (text) {
|
||||
return FORMAT_REGEX.test(text);
|
||||
};
|
||||
|
||||
SinewaveDeltaFormat.prototype.parse = function (text) {
|
||||
var negative = text[0] === "-",
|
||||
parts = text.replace("-", "").split(":");
|
||||
return [ HOUR, MINUTE, SECOND ].map(function (sz, i) {
|
||||
return parseInt(parts[i], 10) * sz;
|
||||
}).reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0) * (negative ? -1 : 1) + START_TIME;
|
||||
};
|
||||
|
||||
return SinewaveDeltaFormat;
|
||||
}
|
||||
);
|
@ -25,12 +25,12 @@
|
||||
* Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
['./SinewaveConstants'],
|
||||
function (SinewaveConstants) {
|
||||
"use strict";
|
||||
|
||||
var ONE_DAY = 60 * 60 * 24,
|
||||
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
|
||||
firstObservedTime = Math.floor(SinewaveConstants.START_TIME / 1000);
|
||||
|
||||
/**
|
||||
*
|
||||
@ -58,6 +58,9 @@ define(
|
||||
};
|
||||
|
||||
generatorData.getDomainValue = function (i, domain) {
|
||||
// delta uses the same numeric values as the default domain,
|
||||
// so it's not checked for here, just formatted for display
|
||||
// differently.
|
||||
return (i + offset) * 1000 + firstTime * 1000 -
|
||||
(domain === 'yesterday' ? ONE_DAY : 0);
|
||||
};
|
||||
|
26
platform/commonUI/formats/bundle.json
Normal file
26
platform/commonUI/formats/bundle.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Time services bundle",
|
||||
"description": "Defines interfaces and provides default implementations for handling different time systems.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"provides": "formatService",
|
||||
"type": "provider",
|
||||
"implementation": "FormatProvider.js",
|
||||
"depends": [ "formats[]", "$log" ]
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "utc",
|
||||
"implementation": "UTCTimeFormat.js"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "DEFAULT_TIME_FORMAT",
|
||||
"value": "utc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
113
platform/commonUI/formats/src/FormatProvider.js
Normal file
113
platform/commonUI/formats/src/FormatProvider.js
Normal file
@ -0,0 +1,113 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* An object used to convert between numeric values and text values,
|
||||
* typically used to display these values to the user and to convert
|
||||
* user input to a numeric format, particularly for time formats.
|
||||
* @interface {Format}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse text (typically user input) to a numeric value.
|
||||
* Behavior is undefined when the text cannot be parsed;
|
||||
* `validate` should be called first if the text may be invalid.
|
||||
* @method parse
|
||||
* @memberof Format#
|
||||
* @param {string} text the text to parse
|
||||
* @returns {number} the parsed numeric value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine whether or not some text (typically user input) can
|
||||
* be parsed to a numeric value by this format.
|
||||
* @method validate
|
||||
* @memberof Format#
|
||||
* @param {string} text the text to parse
|
||||
* @returns {boolean} true if the text can be parsed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a numeric value to a text value for display using
|
||||
* this format.
|
||||
* @method format
|
||||
* @memberof Format#
|
||||
* @param {number} value the numeric value to format
|
||||
* @returns {string} the text representation of the value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides access to `Format` objects which can be used to
|
||||
* convert values between human-readable text and numeric
|
||||
* representations.
|
||||
* @interface FormatService
|
||||
*/
|
||||
|
||||
/**
|
||||
* Look up a format by its symbolic identifier.
|
||||
* @param {string} key the identifier for this format
|
||||
* @returns {Format} the format, or `undefined` if no such format
|
||||
* is known.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides formats from the `formats` extension category.
|
||||
* @constructor
|
||||
* @implements {FormatService}
|
||||
* @memberof platform/commonUI/formats
|
||||
* @param {Array.<function(new : Format)>} format constructors,
|
||||
* from the `formats` extension category.
|
||||
*/
|
||||
function FormatProvider(formats, $log) {
|
||||
var formatMap = {};
|
||||
|
||||
function addToMap(Format) {
|
||||
var key = Format.key;
|
||||
if (key && !formatMap[key]) {
|
||||
formatMap[key] = new Format();
|
||||
}
|
||||
}
|
||||
|
||||
formats.forEach(addToMap);
|
||||
this.formatMap = formatMap;
|
||||
this.$log = $log;
|
||||
}
|
||||
|
||||
FormatProvider.prototype.getFormat = function (key) {
|
||||
var format = this.formatMap[key];
|
||||
if (!format) {
|
||||
this.$log.warn("No format found for " + key);
|
||||
}
|
||||
return format;
|
||||
};
|
||||
|
||||
return FormatProvider;
|
||||
|
||||
});
|
63
platform/commonUI/formats/src/UTCTimeFormat.js
Normal file
63
platform/commonUI/formats/src/UTCTimeFormat.js
Normal file
@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'moment'
|
||||
], function (
|
||||
moment
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
||||
DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Formatter for UTC timestamps. Interprets numeric values as
|
||||
* milliseconds since the start of 1970.
|
||||
*
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function UTCTimeFormat() {
|
||||
}
|
||||
|
||||
UTCTimeFormat.prototype.format = function (value) {
|
||||
return moment.utc(value).format(DATE_FORMAT);
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS).isValid();
|
||||
};
|
||||
|
||||
return UTCTimeFormat;
|
||||
});
|
70
platform/commonUI/formats/test/FormatProviderSpec.js
Normal file
70
platform/commonUI/formats/test/FormatProviderSpec.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
['../src/FormatProvider'],
|
||||
function (FormatProvider) {
|
||||
'use strict';
|
||||
|
||||
var KEYS = [ 'a', 'b', 'c' ];
|
||||
|
||||
describe("The FormatProvider", function () {
|
||||
var mockFormats,
|
||||
mockLog,
|
||||
mockFormatInstances,
|
||||
provider;
|
||||
|
||||
beforeEach(function () {
|
||||
mockFormatInstances = KEYS.map(function (k) {
|
||||
return jasmine.createSpyObj(
|
||||
'format-' + k,
|
||||
[ 'parse', 'validate', 'format' ]
|
||||
);
|
||||
});
|
||||
mockLog = jasmine.createSpyObj('$log', ['error', 'warn']);
|
||||
// Return constructors
|
||||
mockFormats = KEYS.map(function (k, i) {
|
||||
function MockFormat() { return mockFormatInstances[i]; }
|
||||
MockFormat.key = k;
|
||||
return MockFormat;
|
||||
});
|
||||
provider = new FormatProvider(mockFormats, mockLog);
|
||||
});
|
||||
|
||||
it("looks up formats by key", function () {
|
||||
KEYS.forEach(function (k, i) {
|
||||
expect(provider.getFormat(k))
|
||||
.toEqual(mockFormatInstances[i]);
|
||||
});
|
||||
});
|
||||
|
||||
it("warns about unknown formats", function () {
|
||||
provider.getFormat('a'); // known format
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
provider.getFormat('some-unknown-format');
|
||||
expect(mockLog.warn).toHaveBeenCalledWith(jasmine.any(String));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
56
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
56
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
@ -0,0 +1,56 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
['../src/UTCTimeFormat', 'moment'],
|
||||
function (UTCTimeFormat, moment) {
|
||||
'use strict';
|
||||
|
||||
describe("The UTCTimeFormat", function () {
|
||||
var format;
|
||||
|
||||
beforeEach(function () {
|
||||
format = new UTCTimeFormat();
|
||||
});
|
||||
|
||||
it("formats UTC timestamps", function () {
|
||||
var timestamp = 12345670000,
|
||||
formatted = format.format(timestamp);
|
||||
expect(formatted).toEqual(jasmine.any(String));
|
||||
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||
});
|
||||
|
||||
it("validates time inputs", function () {
|
||||
expect(format.validate("1977-05-25 11:21:22")).toBe(true);
|
||||
expect(format.validate("garbage text")).toBe(false);
|
||||
});
|
||||
|
||||
it("parses valid input", function () {
|
||||
var text = "1977-05-25 11:21:22",
|
||||
parsed = format.parse(text);
|
||||
expect(parsed).toEqual(jasmine.any(Number));
|
||||
expect(parsed).toEqual(moment.utc(text).valueOf());
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
4
platform/commonUI/formats/test/suite.json
Normal file
4
platform/commonUI/formats/test/suite.json
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
"FormatProvider",
|
||||
"UTCTimeFormat"
|
||||
]
|
@ -53,13 +53,18 @@
|
||||
{
|
||||
"key": "TimeRangeController",
|
||||
"implementation": "controllers/TimeRangeController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "DateTimePickerController",
|
||||
"implementation": "controllers/DateTimePickerController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "DateTimeFieldController",
|
||||
"implementation": "controllers/DateTimeFieldController.js",
|
||||
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT" ]
|
||||
},
|
||||
{
|
||||
"key": "TreeNodeController",
|
||||
"implementation": "controllers/TreeNodeController.js",
|
||||
@ -242,6 +247,10 @@
|
||||
{
|
||||
"key": "datetime-picker",
|
||||
"templateUrl": "templates/controls/datetime-picker.html"
|
||||
},
|
||||
{
|
||||
"key": "datetime-field",
|
||||
"templateUrl": "templates/controls/datetime-field.html"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
|
@ -0,0 +1,20 @@
|
||||
<span class="s-btn"
|
||||
ng-controller="DateTimeFieldController">
|
||||
<input type="text"
|
||||
ng-model="textValue"
|
||||
ng-class="{ error: textInvalid }">
|
||||
</input>
|
||||
<a class="ui-symbol icon icon-calendar"
|
||||
ng-if="structure.format === 'utc' || !structure.format"
|
||||
ng-click="pickerActive = !pickerActive">
|
||||
</a>
|
||||
<mct-popup ng-if="pickerActive">
|
||||
<div mct-click-elsewhere="pickerActive = false">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="ngModel"
|
||||
field="field"
|
||||
options="{ hours: true }">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
</span>
|
@ -22,47 +22,24 @@
|
||||
<div ng-controller="TimeRangeController">
|
||||
<div class="l-time-range-inputs-holder">
|
||||
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
||||
<span class="l-time-range-input" ng-controller="ToggleController as t1">
|
||||
<!--<span class="lbl">Start</span>-->
|
||||
<span class="s-btn time-range-start">
|
||||
<input type="text"
|
||||
ng-model="boundsModel.start"
|
||||
ng-class="{ error: !boundsModel.startValid }">
|
||||
</input>
|
||||
<a class="ui-symbol icon icon-calendar" ng-click="t1.toggle()"></a>
|
||||
<mct-popup ng-if="t1.isActive()">
|
||||
<div mct-click-elsewhere="t1.setState(false)">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="ngModel.outer"
|
||||
field="'start'"
|
||||
options="{ hours: true }">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
</span>
|
||||
<span class="l-time-range-input">
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{ format: parameters.format }"
|
||||
ng-model="ngModel.outer"
|
||||
field="'start'"
|
||||
class="time-range-start">
|
||||
</mct-control>
|
||||
</span>
|
||||
|
||||
<span class="l-time-range-inputs-elem lbl">to</span>
|
||||
|
||||
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||
<!--<span class="lbl">End</span>-->
|
||||
<span class="s-btn l-time-range-input">
|
||||
<input type="text"
|
||||
ng-model="boundsModel.end"
|
||||
ng-class="{ error: !boundsModel.endValid }">
|
||||
</input>
|
||||
<a class="ui-symbol icon icon-calendar" ng-click="t2.toggle()">
|
||||
</a>
|
||||
<mct-popup ng-if="t2.isActive()">
|
||||
<div mct-click-elsewhere="t2.setState(false)">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="ngModel.outer"
|
||||
field="'end'"
|
||||
options="{ hours: true }">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
</span>
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{ format: parameters.format }"
|
||||
ng-model="ngModel.outer"
|
||||
field="'end'"
|
||||
class="time-range-end">
|
||||
</mct-control>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -97,7 +74,7 @@
|
||||
<div class="l-time-range-ticks-holder">
|
||||
<div class="l-time-range-ticks">
|
||||
<div
|
||||
ng-repeat="tick in ticks"
|
||||
ng-repeat="tick in ticks track by $index"
|
||||
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
||||
class="tick tick-x"
|
||||
>
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
var UNRECOGNIZED_FORMAT_ERROR =
|
||||
"Unrecognized format for date-time field.";
|
||||
|
||||
/**
|
||||
* Controller to support the date-time entry field.
|
||||
*
|
||||
* Accepts a `format` property in the `structure` attribute
|
||||
* which allows a date/time to be specified via its symbolic
|
||||
* key (as will be used to look up said format from the
|
||||
* `formatService`.)
|
||||
*
|
||||
* {@see FormatService}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/general
|
||||
* @param $scope the Angular scope for this controller
|
||||
* @param {FormatService} formatService the service to user to format
|
||||
* domain values
|
||||
* @param {string} defaultFormat the format to request when no
|
||||
* format has been otherwise specified
|
||||
*/
|
||||
function DateTimeFieldController($scope, formatService, defaultFormat) {
|
||||
var formatter = formatService.getFormat(defaultFormat);
|
||||
|
||||
function updateFromModel(value) {
|
||||
// Only reformat if the value is different from user
|
||||
// input (to avoid reformatting valid input while typing.)
|
||||
if (!formatter.validate($scope.textValue) ||
|
||||
formatter.parse($scope.textValue) !== value) {
|
||||
$scope.textValue = formatter.format(value);
|
||||
$scope.textInvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFromView(textValue) {
|
||||
$scope.textInvalid = !formatter.validate(textValue);
|
||||
if (!$scope.textInvalid) {
|
||||
$scope.ngModel[$scope.field] =
|
||||
formatter.parse(textValue);
|
||||
}
|
||||
}
|
||||
|
||||
function setFormat(format) {
|
||||
formatter = formatService.getFormat(format || defaultFormat);
|
||||
if (!formatter) {
|
||||
throw new Error(UNRECOGNIZED_FORMAT_ERROR);
|
||||
}
|
||||
updateFromModel($scope.ngModel[$scope.field]);
|
||||
}
|
||||
|
||||
$scope.$watch('structure.format', setFormat);
|
||||
$scope.$watch('ngModel[field]', updateFromModel);
|
||||
$scope.$watch('textValue', updateFromView);
|
||||
}
|
||||
|
||||
return DateTimeFieldController;
|
||||
}
|
||||
);
|
@ -26,33 +26,34 @@ define(
|
||||
function (moment) {
|
||||
"use strict";
|
||||
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
||||
TICK_SPACING_PX = 150;
|
||||
var TICK_SPACING_PX = 150,
|
||||
UNRECOGNIZED_FORMAT_ERROR =
|
||||
"Unrecognized format for time range control.";
|
||||
|
||||
|
||||
/**
|
||||
* Controller used by the `time-controller` template.
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
* @param $scope the Angular scope for this controller
|
||||
* @param {FormatService} formatService the service to user to format
|
||||
* domain values
|
||||
* @param {string} defaultFormat the format to request when no
|
||||
* format has been otherwise specified
|
||||
* @param {Function} now a function to return current system time
|
||||
*/
|
||||
function TimeConductorController($scope, now) {
|
||||
function TimeRangeController($scope, formatService, defaultFormat, now) {
|
||||
var tickCount = 2,
|
||||
innerMinimumSpan = 1000, // 1 second
|
||||
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
|
||||
initialDragValue;
|
||||
initialDragValue,
|
||||
formatter = formatService.getFormat(defaultFormat);
|
||||
|
||||
function formatTimestamp(ts) {
|
||||
return moment.utc(ts).format(DATE_FORMAT);
|
||||
return formatter.format(ts);
|
||||
}
|
||||
|
||||
function parseTimestamp(text) {
|
||||
var m = moment.utc(text, DATE_FORMAT);
|
||||
if (m.isValid()) {
|
||||
return m.valueOf();
|
||||
} else {
|
||||
throw new Error("Could not parse " + text);
|
||||
}
|
||||
}
|
||||
|
||||
// From 0.0-1.0 to "0%"-"1%"
|
||||
// From 0.0-1.0 to "0%"-"100%"
|
||||
function toPercent(p) {
|
||||
return (100 * p) + "%";
|
||||
}
|
||||
@ -101,41 +102,15 @@ define(
|
||||
return { start: bounds.start, end: bounds.end };
|
||||
}
|
||||
|
||||
function updateBoundsTextForProperty(ngModel, property) {
|
||||
try {
|
||||
if (!$scope.boundsModel[property] ||
|
||||
parseTimestamp($scope.boundsModel[property]) !==
|
||||
ngModel.outer[property]) {
|
||||
$scope.boundsModel[property] =
|
||||
formatTimestamp(ngModel.outer[property]);
|
||||
}
|
||||
} catch (e) {
|
||||
// User-entered text is invalid, so leave it be
|
||||
// until they fix it.
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoundsText(ngModel) {
|
||||
updateBoundsTextForProperty(ngModel, 'start');
|
||||
updateBoundsTextForProperty(ngModel, 'end');
|
||||
}
|
||||
|
||||
function updateViewFromModel(ngModel) {
|
||||
var t = now();
|
||||
|
||||
ngModel = ngModel || {};
|
||||
ngModel.outer = ngModel.outer || defaultBounds();
|
||||
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
||||
|
||||
// First, dates for the date pickers for outer bounds
|
||||
updateBoundsText(ngModel);
|
||||
|
||||
// Then various updates for the inner span
|
||||
updateViewForInnerSpanFromModel(ngModel);
|
||||
|
||||
// Stick it back is scope (in case we just set defaults)
|
||||
$scope.ngModel = ngModel;
|
||||
|
||||
updateViewForInnerSpanFromModel(ngModel);
|
||||
updateTicks();
|
||||
}
|
||||
|
||||
@ -155,7 +130,8 @@ define(
|
||||
}
|
||||
|
||||
function toMillis(pixels) {
|
||||
var span = $scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
||||
var span =
|
||||
$scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
||||
return (pixels / $scope.spanWidth) * span;
|
||||
}
|
||||
|
||||
@ -243,36 +219,15 @@ define(
|
||||
updateTicks();
|
||||
}
|
||||
|
||||
function updateStartFromText(value) {
|
||||
try {
|
||||
updateOuterStart(parseTimestamp(value));
|
||||
updateBoundsTextForProperty($scope.ngModel, 'end');
|
||||
$scope.boundsModel.startValid = true;
|
||||
} catch (e) {
|
||||
$scope.boundsModel.startValid = false;
|
||||
return;
|
||||
function updateFormat(key) {
|
||||
formatter = formatService.getFormat(key || defaultFormat);
|
||||
|
||||
if (!formatter) {
|
||||
throw new Error(UNRECOGNIZED_FORMAT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
function updateEndFromText(value) {
|
||||
try {
|
||||
updateOuterEnd(parseTimestamp(value));
|
||||
updateBoundsTextForProperty($scope.ngModel, 'start');
|
||||
$scope.boundsModel.endValid = true;
|
||||
} catch (e) {
|
||||
$scope.boundsModel.endValid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function updateStartFromPicker(value) {
|
||||
updateOuterStart(value);
|
||||
updateBoundsText($scope.ngModel);
|
||||
}
|
||||
|
||||
function updateEndFromPicker(value) {
|
||||
updateOuterEnd(value);
|
||||
updateBoundsText($scope.ngModel);
|
||||
updateViewForInnerSpanFromModel($scope.ngModel);
|
||||
updateTicks();
|
||||
}
|
||||
|
||||
$scope.startLeftDrag = startLeftDrag;
|
||||
@ -282,21 +237,18 @@ define(
|
||||
$scope.rightDrag = rightDrag;
|
||||
$scope.middleDrag = middleDrag;
|
||||
|
||||
$scope.state = false;
|
||||
$scope.ticks = [];
|
||||
$scope.boundsModel = {};
|
||||
|
||||
// Initialize scope to defaults
|
||||
updateViewFromModel($scope.ngModel);
|
||||
|
||||
$scope.$watchCollection("ngModel", updateViewFromModel);
|
||||
$scope.$watch("spanWidth", updateSpanWidth);
|
||||
$scope.$watch("ngModel.outer.start", updateStartFromPicker);
|
||||
$scope.$watch("ngModel.outer.end", updateEndFromPicker);
|
||||
$scope.$watch("boundsModel.start", updateStartFromText);
|
||||
$scope.$watch("boundsModel.end", updateEndFromText);
|
||||
$scope.$watch("ngModel.outer.start", updateOuterStart);
|
||||
$scope.$watch("ngModel.outer.end", updateOuterEnd);
|
||||
$scope.$watch("parameters.format", updateFormat);
|
||||
}
|
||||
|
||||
return TimeConductorController;
|
||||
return TimeRangeController;
|
||||
}
|
||||
);
|
||||
|
@ -0,0 +1,183 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/DateTimeFieldController", "moment"],
|
||||
function (DateTimeFieldController, moment) {
|
||||
'use strict';
|
||||
|
||||
var TEST_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||
|
||||
describe("The DateTimeFieldController", function () {
|
||||
var mockScope,
|
||||
mockFormatService,
|
||||
mockFormat,
|
||||
controller;
|
||||
|
||||
function fireWatch(expr, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$watch']);
|
||||
mockFormatService =
|
||||
jasmine.createSpyObj('formatService', ['getFormat']);
|
||||
mockFormat = jasmine.createSpyObj('format', [
|
||||
'parse',
|
||||
'validate',
|
||||
'format'
|
||||
]);
|
||||
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
|
||||
mockFormat.validate.andCallFake(function (text) {
|
||||
return moment.utc(text, TEST_FORMAT).isValid();
|
||||
});
|
||||
mockFormat.parse.andCallFake(function (text) {
|
||||
return moment.utc(text, TEST_FORMAT).valueOf();
|
||||
});
|
||||
mockFormat.format.andCallFake(function (value) {
|
||||
return moment.utc(value).format(TEST_FORMAT);
|
||||
});
|
||||
|
||||
mockScope.ngModel = { testField: 12321 };
|
||||
mockScope.field = "testField";
|
||||
mockScope.structure = { format: "someFormat" };
|
||||
|
||||
controller = new DateTimeFieldController(
|
||||
mockScope,
|
||||
mockFormatService
|
||||
);
|
||||
});
|
||||
|
||||
it("updates models from user-entered text", function () {
|
||||
var newText = "1977-05-25 17:30:00";
|
||||
|
||||
mockScope.textValue = newText;
|
||||
fireWatch("textValue", newText);
|
||||
expect(mockScope.ngModel.testField)
|
||||
.toEqual(mockFormat.parse(newText));
|
||||
expect(mockScope.textInvalid).toBeFalsy();
|
||||
});
|
||||
|
||||
it("updates text from model values", function () {
|
||||
var testTime = mockFormat.parse("1977-05-25 17:30:00");
|
||||
mockScope.ngModel.testField = testTime;
|
||||
fireWatch("ngModel[field]", testTime);
|
||||
expect(mockScope.textValue).toEqual("1977-05-25 17:30:00");
|
||||
});
|
||||
|
||||
describe("when user input is invalid", function () {
|
||||
var newText, oldValue;
|
||||
|
||||
beforeEach(function () {
|
||||
newText = "Not a date";
|
||||
oldValue = mockScope.ngModel.testField;
|
||||
mockScope.textValue = newText;
|
||||
fireWatch("textValue", newText);
|
||||
});
|
||||
|
||||
it("displays error state", function () {
|
||||
expect(mockScope.textInvalid).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not modify model state", function () {
|
||||
expect(mockScope.ngModel.testField).toEqual(oldValue);
|
||||
});
|
||||
|
||||
it("does not modify user input", function () {
|
||||
expect(mockScope.textValue).toEqual(newText);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not modify valid but irregular user input", function () {
|
||||
// Don't want the controller "fixing" bad or
|
||||
// irregularly-formatted input out from under
|
||||
// the user's fingertips.
|
||||
var newText = "2015-3-3 01:02:04",
|
||||
oldValue = mockScope.ngModel.testField;
|
||||
|
||||
mockFormat.validate.andReturn(true);
|
||||
mockFormat.parse.andReturn(42);
|
||||
mockScope.textValue = newText;
|
||||
fireWatch("textValue", newText);
|
||||
|
||||
expect(mockScope.textValue).toEqual(newText);
|
||||
expect(mockScope.ngModel.testField).toEqual(42);
|
||||
expect(mockScope.ngModel.testField).not.toEqual(oldValue);
|
||||
});
|
||||
|
||||
it("obtains a format from the format service", function () {
|
||||
fireWatch('structure.format', mockScope.structure.format);
|
||||
expect(mockFormatService.getFormat)
|
||||
.toHaveBeenCalledWith(mockScope.structure.format);
|
||||
});
|
||||
|
||||
it("throws an error for unknown formats", function () {
|
||||
mockFormatService.getFormat.andReturn(undefined);
|
||||
expect(function () {
|
||||
fireWatch("structure.format", "some-format");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe("using the obtained format", function () {
|
||||
var testValue = 1234321,
|
||||
testText = "some text";
|
||||
|
||||
beforeEach(function () {
|
||||
mockFormat.validate.andReturn(true);
|
||||
mockFormat.parse.andReturn(testValue);
|
||||
mockFormat.format.andReturn(testText);
|
||||
});
|
||||
|
||||
it("parses user input", function () {
|
||||
var newText = "some other new text";
|
||||
mockScope.textValue = newText;
|
||||
fireWatch("textValue", newText);
|
||||
expect(mockFormat.parse).toHaveBeenCalledWith(newText);
|
||||
expect(mockScope.ngModel.testField).toEqual(testValue);
|
||||
});
|
||||
|
||||
it("validates user input", function () {
|
||||
var newText = "some other new text";
|
||||
mockScope.textValue = newText;
|
||||
fireWatch("textValue", newText);
|
||||
expect(mockFormat.validate).toHaveBeenCalledWith(newText);
|
||||
});
|
||||
|
||||
it("formats model data for display", function () {
|
||||
var newValue = 42;
|
||||
mockScope.ngModel.testField = newValue;
|
||||
fireWatch("ngModel[field]", newValue);
|
||||
expect(mockFormat.format).toHaveBeenCalledWith(newValue);
|
||||
expect(mockScope.textValue).toEqual(testText);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -33,7 +33,10 @@ define(
|
||||
|
||||
describe("The TimeRangeController", function () {
|
||||
var mockScope,
|
||||
mockFormatService,
|
||||
testDefaultFormat,
|
||||
mockNow,
|
||||
mockFormat,
|
||||
controller;
|
||||
|
||||
function fireWatch(expr, value) {
|
||||
@ -57,8 +60,30 @@ define(
|
||||
"$scope",
|
||||
[ "$apply", "$watch", "$watchCollection" ]
|
||||
);
|
||||
mockFormatService = jasmine.createSpyObj(
|
||||
"formatService",
|
||||
[ "getFormat" ]
|
||||
);
|
||||
testDefaultFormat = 'utc';
|
||||
mockFormat = jasmine.createSpyObj(
|
||||
"format",
|
||||
[ "validate", "format", "parse" ]
|
||||
);
|
||||
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
|
||||
mockFormat.format.andCallFake(function (value) {
|
||||
return moment.utc(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
});
|
||||
|
||||
mockNow = jasmine.createSpy('now');
|
||||
controller = new TimeRangeController(mockScope, mockNow);
|
||||
|
||||
controller = new TimeRangeController(
|
||||
mockScope,
|
||||
mockFormatService,
|
||||
testDefaultFormat,
|
||||
mockNow
|
||||
);
|
||||
});
|
||||
|
||||
it("watches the model that was passed in", function () {
|
||||
@ -167,70 +192,22 @@ define(
|
||||
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
||||
});
|
||||
|
||||
describe("by typing", function () {
|
||||
it("updates models", function () {
|
||||
var newStart = "1977-05-25 17:30:00",
|
||||
newEnd = "2015-12-18 03:30:00";
|
||||
|
||||
mockScope.boundsModel.start = newStart;
|
||||
fireWatch("boundsModel.start", newStart);
|
||||
expect(mockScope.ngModel.outer.start)
|
||||
.toEqual(moment.utc(newStart).valueOf());
|
||||
expect(mockScope.boundsModel.startValid)
|
||||
.toBeTruthy();
|
||||
|
||||
mockScope.boundsModel.end = newEnd;
|
||||
fireWatch("boundsModel.end", newEnd);
|
||||
expect(mockScope.ngModel.outer.end)
|
||||
.toEqual(moment.utc(newEnd).valueOf());
|
||||
expect(mockScope.boundsModel.endValid)
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("displays error state", function () {
|
||||
var newStart = "Not a date",
|
||||
newEnd = "Definitely not a date",
|
||||
oldStart = mockScope.ngModel.outer.start,
|
||||
oldEnd = mockScope.ngModel.outer.end;
|
||||
|
||||
mockScope.boundsModel.start = newStart;
|
||||
fireWatch("boundsModel.start", newStart);
|
||||
expect(mockScope.ngModel.outer.start)
|
||||
.toEqual(oldStart);
|
||||
expect(mockScope.boundsModel.startValid)
|
||||
.toBeFalsy();
|
||||
|
||||
mockScope.boundsModel.end = newEnd;
|
||||
fireWatch("boundsModel.end", newEnd);
|
||||
expect(mockScope.ngModel.outer.end)
|
||||
.toEqual(oldEnd);
|
||||
expect(mockScope.boundsModel.endValid)
|
||||
.toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not modify user input", function () {
|
||||
// Don't want the controller "fixing" bad or
|
||||
// irregularly-formatted input out from under
|
||||
// the user's fingertips.
|
||||
var newStart = "Not a date",
|
||||
newEnd = "2015-3-3 01:02:04",
|
||||
oldStart = mockScope.ngModel.outer.start,
|
||||
oldEnd = mockScope.ngModel.outer.end;
|
||||
|
||||
mockScope.boundsModel.start = newStart;
|
||||
fireWatch("boundsModel.start", newStart);
|
||||
expect(mockScope.boundsModel.start)
|
||||
.toEqual(newStart);
|
||||
|
||||
mockScope.boundsModel.end = newEnd;
|
||||
fireWatch("boundsModel.end", newEnd);
|
||||
expect(mockScope.boundsModel.end)
|
||||
.toEqual(newEnd);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("watches for changes in format selection", function () {
|
||||
expect(mockFormatService.getFormat)
|
||||
.not.toHaveBeenCalledWith('test-format');
|
||||
fireWatch("parameters.format", 'test-format');
|
||||
expect(mockFormatService.getFormat)
|
||||
.toHaveBeenCalledWith('test-format');
|
||||
});
|
||||
|
||||
it("throws an error for unknown formats", function () {
|
||||
mockFormatService.getFormat.andReturn(undefined);
|
||||
expect(function () {
|
||||
fireWatch("parameters.format", "some-format");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
"controllers/BottomBarController",
|
||||
"controllers/ClickAwayController",
|
||||
"controllers/ContextMenuController",
|
||||
"controllers/DateTimeFieldController",
|
||||
"controllers/DateTimePickerController",
|
||||
"controllers/GetterSetterController",
|
||||
"controllers/SelectorController",
|
||||
|
@ -36,9 +36,9 @@
|
||||
{
|
||||
"key": "TIME_CONDUCTOR_DOMAINS",
|
||||
"value": [
|
||||
{ "key": "time", "name": "Time" },
|
||||
{ "key": "yesterday", "name": "Yesterday" }
|
||||
{ "key": "time", "name": "UTC", "format": "utc" }
|
||||
],
|
||||
"priority": "fallback",
|
||||
"comment": "Placeholder; to be replaced by inspection of available domains."
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,5 @@
|
||||
<mct-include key="'time-controller'"
|
||||
parameters='parameters'
|
||||
ng-model='ngModel.conductor'>
|
||||
</mct-include>
|
||||
<mct-control key="'select'"
|
||||
|
@ -27,7 +27,10 @@ define(
|
||||
"use strict";
|
||||
|
||||
var TEMPLATE = [
|
||||
"<mct-include key=\"'time-conductor'\" ng-model='ngModel' class='l-time-controller'>",
|
||||
"<mct-include key=\"'time-conductor'\" ",
|
||||
"ng-model='ngModel' ",
|
||||
"parameters='parameters' ",
|
||||
"class='l-time-controller'>",
|
||||
"</mct-include>"
|
||||
].join(''),
|
||||
THROTTLE_MS = 200,
|
||||
@ -74,11 +77,11 @@ define(
|
||||
broadcastBounds;
|
||||
|
||||
// Combine start/end times into a single object
|
||||
function bounds(start, end) {
|
||||
function bounds() {
|
||||
return {
|
||||
start: conductor.displayStart(),
|
||||
end: conductor.displayEnd(),
|
||||
domain: conductor.domain()
|
||||
domain: conductor.domain().key
|
||||
};
|
||||
}
|
||||
|
||||
@ -97,12 +100,10 @@ define(
|
||||
}
|
||||
|
||||
function updateDomain(value) {
|
||||
conductor.domain(value);
|
||||
repScope.$broadcast('telemetry:display:bounds', bounds(
|
||||
conductor.displayStart(),
|
||||
conductor.displayEnd(),
|
||||
conductor.domain()
|
||||
));
|
||||
var newDomain = conductor.domain(value);
|
||||
conductorScope.parameters.format =
|
||||
newDomain && newDomain.format;
|
||||
repScope.$broadcast('telemetry:display:bounds', bounds());
|
||||
}
|
||||
|
||||
// telemetry domain metadata -> option for a select control
|
||||
@ -130,7 +131,8 @@ define(
|
||||
{ outer: bounds(), inner: bounds() };
|
||||
conductorScope.ngModel.options =
|
||||
conductor.domainOptions().map(makeOption);
|
||||
conductorScope.ngModel.domain = conductor.domain();
|
||||
conductorScope.ngModel.domain = conductor.domain().key;
|
||||
conductorScope.parameters = {};
|
||||
|
||||
conductorScope
|
||||
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
||||
|
@ -51,7 +51,7 @@ define(
|
||||
request = request || {};
|
||||
request.start = start;
|
||||
request.end = end;
|
||||
request.domain = domain;
|
||||
request.domain = domain.key;
|
||||
return request;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ define(
|
||||
function TimeConductor(start, end, domains) {
|
||||
this.range = { start: start, end: end };
|
||||
this.domains = domains;
|
||||
this.activeDomain = domains[0].key;
|
||||
this.activeDomain = domains[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +73,7 @@ define(
|
||||
/**
|
||||
* Get available domain options which can be used to bound time
|
||||
* selection.
|
||||
* @returns {TelemetryDomain[]} available domains
|
||||
* @returns {TelemetryDomainMetadata[]} available domains
|
||||
*/
|
||||
TimeConductor.prototype.domainOptions = function () {
|
||||
return this.domains;
|
||||
@ -82,19 +82,21 @@ define(
|
||||
/**
|
||||
* 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
|
||||
* @returns {TelemetryDomainMetadata} the active telemetry domain
|
||||
*/
|
||||
TimeConductor.prototype.domain = function (key) {
|
||||
function matchesKey(domain) {
|
||||
return domain.key === key;
|
||||
}
|
||||
var i;
|
||||
|
||||
if (arguments.length > 0) {
|
||||
if (!this.domains.some(matchesKey)) {
|
||||
throw new Error("Unknown domain " + key);
|
||||
for (i = 0; i < this.domains.length; i += 1) {
|
||||
if (this.domains[i].key === key) {
|
||||
return (this.activeDomain = this.domains[i]);
|
||||
}
|
||||
}
|
||||
this.activeDomain = key;
|
||||
|
||||
throw new Error("Unknown domain " + key);
|
||||
}
|
||||
|
||||
return this.activeDomain;
|
||||
};
|
||||
|
||||
|
@ -129,7 +129,7 @@ define(
|
||||
it("exposes conductor state in scope", function () {
|
||||
mockConductor.displayStart.andReturn(1977);
|
||||
mockConductor.displayEnd.andReturn(1984);
|
||||
mockConductor.domain.andReturn('d');
|
||||
mockConductor.domain.andReturn({ key: 'd' });
|
||||
representer.represent(testViews[0], {});
|
||||
|
||||
expect(mockNewScope.ngModel.conductor).toEqual({
|
||||
@ -219,7 +219,7 @@ define(
|
||||
representer.represent(testViews[0], null);
|
||||
|
||||
expect(mockNewScope.ngModel.domain)
|
||||
.toEqual(mockConductor.domain());
|
||||
.toEqual(mockConductor.domain().key);
|
||||
});
|
||||
|
||||
it("exposes domain options in scope", function () {
|
||||
|
@ -77,7 +77,7 @@ define(
|
||||
|
||||
mockConductor.displayStart.andReturn(42);
|
||||
mockConductor.displayEnd.andReturn(1977);
|
||||
mockConductor.domain.andReturn("testDomain");
|
||||
mockConductor.domain.andReturn({ key: "testDomain" });
|
||||
|
||||
decorator = new ConductorTelemetryDecorator(
|
||||
mockConductorService,
|
||||
@ -104,7 +104,7 @@ define(
|
||||
});
|
||||
|
||||
it("with domain selection", function () {
|
||||
expect(request.domain).toEqual(mockConductor.domain());
|
||||
expect(request.domain).toEqual(mockConductor.domain().key);
|
||||
});
|
||||
});
|
||||
|
||||
@ -127,7 +127,7 @@ define(
|
||||
});
|
||||
|
||||
it("with domain selection", function () {
|
||||
expect(request.domain).toEqual(mockConductor.domain());
|
||||
expect(request.domain).toEqual(mockConductor.domain().key);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -59,12 +59,12 @@ define(
|
||||
});
|
||||
|
||||
it("exposes the current domain choice", function () {
|
||||
expect(conductor.domain()).toEqual(testDomains[0].key);
|
||||
expect(conductor.domain()).toEqual(testDomains[0]);
|
||||
});
|
||||
|
||||
it("allows the domain choice to be changed", function () {
|
||||
conductor.domain(testDomains[1].key);
|
||||
expect(conductor.domain()).toEqual(testDomains[1].key);
|
||||
expect(conductor.domain()).toEqual(testDomains[1]);
|
||||
});
|
||||
|
||||
it("throws an error on attempts to set an invalid domain", function () {
|
||||
|
@ -31,10 +31,19 @@ define(
|
||||
"./elements/PlotPalette",
|
||||
"./elements/PlotAxis",
|
||||
"./elements/PlotLimitTracker",
|
||||
"./elements/PlotTelemetryFormatter",
|
||||
"./modes/PlotModeOptions",
|
||||
"./SubPlotFactory"
|
||||
],
|
||||
function (PlotUpdater, PlotPalette, PlotAxis, PlotLimitTracker, PlotModeOptions, SubPlotFactory) {
|
||||
function (
|
||||
PlotUpdater,
|
||||
PlotPalette,
|
||||
PlotAxis,
|
||||
PlotLimitTracker,
|
||||
PlotTelemetryFormatter,
|
||||
PlotModeOptions,
|
||||
SubPlotFactory
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
var AXIS_DEFAULTS = [
|
||||
@ -62,7 +71,10 @@ define(
|
||||
PLOT_FIXED_DURATION
|
||||
) {
|
||||
var self = this,
|
||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||
plotTelemetryFormatter =
|
||||
new PlotTelemetryFormatter(telemetryFormatter),
|
||||
subPlotFactory =
|
||||
new SubPlotFactory(plotTelemetryFormatter),
|
||||
cachedObjects = [],
|
||||
updater,
|
||||
lastBounds,
|
||||
@ -71,10 +83,9 @@ define(
|
||||
// Populate the scope with axis information (specifically, options
|
||||
// available for each axis.)
|
||||
function setupAxes(metadatas) {
|
||||
$scope.axes = [
|
||||
new PlotAxis("domain", metadatas, AXIS_DEFAULTS[0]),
|
||||
new PlotAxis("range", metadatas, AXIS_DEFAULTS[1])
|
||||
];
|
||||
$scope.axes.forEach(function (axis) {
|
||||
axis.updateMetadata(metadatas);
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger an update of a specific subplot;
|
||||
@ -125,37 +136,49 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
function getUpdater() {
|
||||
if (!updater) {
|
||||
recreateUpdater();
|
||||
}
|
||||
return updater;
|
||||
}
|
||||
|
||||
// Handle new telemetry data in this plot
|
||||
function updateValues() {
|
||||
self.pending = false;
|
||||
if (handle) {
|
||||
setupModes(handle.getTelemetryObjects());
|
||||
}
|
||||
if (updater) {
|
||||
updater.update();
|
||||
setupAxes(handle.getMetadata());
|
||||
getUpdater().update();
|
||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||
}
|
||||
if (self.limitTracker) {
|
||||
self.limitTracker.update();
|
||||
self.update();
|
||||
}
|
||||
self.update();
|
||||
}
|
||||
|
||||
// Display new historical data as it becomes available
|
||||
function addHistoricalData(domainObject, series) {
|
||||
self.pending = false;
|
||||
updater.addHistorical(domainObject, series);
|
||||
getUpdater().addHistorical(domainObject, series);
|
||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||
self.update();
|
||||
}
|
||||
|
||||
// Issue a new request for historical telemetry
|
||||
function requestTelemetry() {
|
||||
if (handle && updater) {
|
||||
if (handle) {
|
||||
handle.request({}, addHistoricalData);
|
||||
}
|
||||
}
|
||||
|
||||
// Requery for data entirely
|
||||
function replot() {
|
||||
if (handle) {
|
||||
updater = undefined;
|
||||
requestTelemetry();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new subscription; telemetrySubscriber gets
|
||||
// to do the meaningful work here.
|
||||
function subscribe(domainObject) {
|
||||
@ -167,12 +190,7 @@ define(
|
||||
updateValues,
|
||||
true // Lossless
|
||||
);
|
||||
if (handle) {
|
||||
setupModes(handle.getTelemetryObjects());
|
||||
setupAxes(handle.getMetadata());
|
||||
recreateUpdater();
|
||||
requestTelemetry();
|
||||
}
|
||||
replot();
|
||||
}
|
||||
|
||||
// Release the current subscription (called when scope is destroyed)
|
||||
@ -185,12 +203,22 @@ define(
|
||||
|
||||
// Respond to a display bounds change (requery for data)
|
||||
function changeDisplayBounds(event, bounds) {
|
||||
var domainAxis = $scope.axes[0];
|
||||
|
||||
domainAxis.chooseOption(bounds.domain);
|
||||
plotTelemetryFormatter
|
||||
.setDomainFormat(domainAxis.active.format);
|
||||
|
||||
self.pending = true;
|
||||
releaseSubscription();
|
||||
subscribe($scope.domainObject);
|
||||
setBasePanZoom(bounds);
|
||||
}
|
||||
|
||||
function updateDomainFormat(format) {
|
||||
plotTelemetryFormatter.setDomainFormat(format);
|
||||
}
|
||||
|
||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||
this.updateValues = updateValues;
|
||||
|
||||
@ -202,6 +230,13 @@ define(
|
||||
|
||||
self.pending = true;
|
||||
|
||||
// Initialize axes; will get repopulated when telemetry
|
||||
// metadata becomes available.
|
||||
$scope.axes = [
|
||||
new PlotAxis("domains", [], AXIS_DEFAULTS[0]),
|
||||
new PlotAxis("ranges", [], AXIS_DEFAULTS[1])
|
||||
];
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
$scope.$watch('domainObject', subscribe);
|
||||
|
||||
|
@ -121,9 +121,9 @@ define(
|
||||
// Utility, for map/forEach loops. Index 0 is domain,
|
||||
// index 1 is range.
|
||||
function formatValue(v, i) {
|
||||
return (i ?
|
||||
formatter.formatRangeValue :
|
||||
formatter.formatDomainValue)(v);
|
||||
return i ?
|
||||
formatter.formatRangeValue(v) :
|
||||
formatter.formatDomainValue(v);
|
||||
}
|
||||
|
||||
this.hoverCoordinates = this.mousePosition &&
|
||||
|
@ -46,21 +46,9 @@ define(
|
||||
*
|
||||
*/
|
||||
function PlotAxis(axisType, metadatas, defaultValue) {
|
||||
var keys = {},
|
||||
options = [];
|
||||
|
||||
// Look through all metadata objects and assemble a list
|
||||
// of all possible domain or range options
|
||||
function buildOptionsForMetadata(m) {
|
||||
(m[axisType] || []).forEach(function (option) {
|
||||
if (!keys[option.key]) {
|
||||
keys[option.key] = true;
|
||||
options.push(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
(metadatas || []).forEach(buildOptionsForMetadata);
|
||||
this.axisType = axisType;
|
||||
this.defaultValue = defaultValue;
|
||||
this.optionKeys = {};
|
||||
|
||||
/**
|
||||
* The currently chosen option for this axis. An
|
||||
@ -68,7 +56,7 @@ define(
|
||||
* directly form the plot template.
|
||||
* @memberof platform/features/plot.PlotAxis#
|
||||
*/
|
||||
this.active = options[0] || defaultValue;
|
||||
this.active = defaultValue;
|
||||
|
||||
/**
|
||||
* The set of options applicable for this axis;
|
||||
@ -77,9 +65,71 @@ define(
|
||||
* human-readable names respectively)
|
||||
* @memberof platform/features/plot.PlotAxis#
|
||||
*/
|
||||
this.options = options;
|
||||
this.options = [];
|
||||
|
||||
// Initialize options from metadata objects
|
||||
this.updateMetadata(metadatas);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update axis options to reflect current metadata.
|
||||
* @param {TelemetryMetadata[]} metadata objects describing
|
||||
* applicable telemetry
|
||||
*/
|
||||
PlotAxis.prototype.updateMetadata = function (metadatas) {
|
||||
var axisType = this.axisType,
|
||||
optionKeys = this.optionKeys,
|
||||
newOptions = {},
|
||||
toAdd = [];
|
||||
|
||||
function isValid(option) {
|
||||
return option && optionKeys[option.key];
|
||||
}
|
||||
|
||||
metadatas.forEach(function (m) {
|
||||
(m[axisType] || []).forEach(function (option) {
|
||||
var key = option.key;
|
||||
if (!optionKeys[key] && !newOptions[key]) {
|
||||
toAdd.push(option);
|
||||
}
|
||||
newOptions[key] = true;
|
||||
});
|
||||
});
|
||||
|
||||
optionKeys = this.optionKeys = newOptions;
|
||||
|
||||
// General approach here is to avoid changing object
|
||||
// instances unless something has really changed, since
|
||||
// Angular is watching; don't want to trigger extra digests.
|
||||
if (!this.options.every(isValid)) {
|
||||
this.options = this.options.filter(isValid);
|
||||
}
|
||||
|
||||
if (toAdd.length > 0) {
|
||||
this.options = this.options.concat(toAdd);
|
||||
}
|
||||
|
||||
if (!isValid(this.active)) {
|
||||
this.active = this.options[0] || this.defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the domain/range selection for this axis. If the
|
||||
* provided `key` is not recognized as an option, no change
|
||||
* will occur.
|
||||
* @param {string} key the identifier for the domain/range
|
||||
*/
|
||||
PlotAxis.prototype.chooseOption = function (key) {
|
||||
var self = this;
|
||||
this.options.forEach(function (option) {
|
||||
if (option.key === key) {
|
||||
self.active = option;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return PlotAxis;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Wraps a `TelemetryFormatter` to provide formats for domain and
|
||||
* range values; provides a single place to track domain/range
|
||||
* formats within a plot, allowing other plot elements to simply
|
||||
* request that values be formatted.
|
||||
* @constructor
|
||||
* @memberof platform/features/plot
|
||||
* @implements {platform/telemetry.TelemetryFormatter}
|
||||
* @param {TelemetryFormatter} telemetryFormatter the formatter
|
||||
* to wrap.
|
||||
*/
|
||||
function PlotTelemetryFormatter(telemetryFormatter) {
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the format to use for domain values.
|
||||
* @param {string} key the format's identifier
|
||||
*/
|
||||
PlotTelemetryFormatter.prototype.setDomainFormat = function (key) {
|
||||
this.domainFormat = key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specify the format to use for range values.
|
||||
* @param {string} key the format's identifier
|
||||
*/
|
||||
PlotTelemetryFormatter.prototype.setRangeFormat = function (key) {
|
||||
this.rangeFormat = key;
|
||||
};
|
||||
|
||||
PlotTelemetryFormatter.prototype.formatDomainValue = function (value) {
|
||||
return this.telemetryFormatter
|
||||
.formatDomainValue(value, this.domainFormat);
|
||||
};
|
||||
|
||||
PlotTelemetryFormatter.prototype.formatRangeValue = function (value) {
|
||||
return this.telemetryFormatter
|
||||
.formatRangeValue(value, this.rangeFormat);
|
||||
};
|
||||
|
||||
return PlotTelemetryFormatter;
|
||||
}
|
||||
);
|
@ -43,6 +43,14 @@ define(
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
// For phantomjs compatibility, for headless testing
|
||||
// (Function.prototype.bind unsupported)
|
||||
function bind(fn, thisObj) {
|
||||
return fn.bind ? fn.bind(thisObj) : function () {
|
||||
return fn.apply(thisObj, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// Generate ticks; interpolate from start up to
|
||||
// start + span in count steps, using the provided
|
||||
// formatter to represent each value.
|
||||
@ -72,7 +80,7 @@ define(
|
||||
panZoom.origin[0],
|
||||
panZoom.dimensions[0],
|
||||
count,
|
||||
this.formatter.formatDomainValue
|
||||
bind(this.formatter.formatDomainValue, this.formatter)
|
||||
);
|
||||
};
|
||||
|
||||
@ -87,7 +95,7 @@ define(
|
||||
panZoom.origin[1],
|
||||
panZoom.dimensions[1],
|
||||
count,
|
||||
this.formatter.formatRangeValue
|
||||
bind(this.formatter.formatRangeValue, this.formatter)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -169,8 +169,9 @@ define(
|
||||
mockDomainObject
|
||||
]);
|
||||
|
||||
// Make an object available
|
||||
// Make an object available; invoke handler's callback
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
|
||||
expect(controller.getModeOptions().length).toEqual(1);
|
||||
|
||||
@ -181,8 +182,9 @@ define(
|
||||
mockDomainObject
|
||||
]);
|
||||
|
||||
// Make an object available
|
||||
// Make an object available; invoke handler's callback
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
|
||||
expect(controller.getModeOptions().length).toEqual(2);
|
||||
});
|
||||
|
@ -30,7 +30,12 @@ define(
|
||||
"use strict";
|
||||
|
||||
describe("A plot axis", function () {
|
||||
var testMetadatas = [
|
||||
var testMetadatas,
|
||||
testDefault,
|
||||
axis;
|
||||
|
||||
beforeEach(function () {
|
||||
testMetadatas = [
|
||||
{
|
||||
tests: [
|
||||
{ key: "t0", name: "T0" },
|
||||
@ -52,13 +57,14 @@ define(
|
||||
{ key: "t6", name: "T6" }
|
||||
]
|
||||
}
|
||||
],
|
||||
testDefault = { key: "test", name: "Test" },
|
||||
controller = new PlotAxis("tests", testMetadatas, testDefault);
|
||||
];
|
||||
testDefault = { key: "test", name: "Test" };
|
||||
axis = new PlotAxis("tests", testMetadatas, testDefault);
|
||||
});
|
||||
|
||||
it("pulls out a list of domain or range options", function () {
|
||||
// Should have filtered out duplicates, etc
|
||||
expect(controller.options).toEqual([
|
||||
expect(axis.options).toEqual([
|
||||
{ key: "t0", name: "T0" },
|
||||
{ key: "t1", name: "T1" },
|
||||
{ key: "t2", name: "T2" },
|
||||
@ -70,7 +76,7 @@ define(
|
||||
});
|
||||
|
||||
it("chooses the first option as a default", function () {
|
||||
expect(controller.active).toEqual({ key: "t0", name: "T0" });
|
||||
expect(axis.active).toEqual({ key: "t0", name: "T0" });
|
||||
});
|
||||
|
||||
it("falls back to a provided default if no options are present", function () {
|
||||
@ -78,6 +84,26 @@ define(
|
||||
.toEqual(testDefault);
|
||||
});
|
||||
|
||||
it("allows options to be chosen by key", function () {
|
||||
axis.chooseOption("t3");
|
||||
expect(axis.active).toEqual({ key: "t3", name: "T3" });
|
||||
});
|
||||
|
||||
it("reflects changes to applicable metadata", function () {
|
||||
axis.updateMetadata([ testMetadatas[1] ]);
|
||||
expect(axis.options).toEqual([
|
||||
{ key: "t0", name: "T0" },
|
||||
{ key: "t2", name: "T2" }
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns the same array instance for unchanged metadata", function () {
|
||||
// ...to avoid triggering extra digest cycles.
|
||||
var oldInstance = axis.options;
|
||||
axis.updateMetadata(testMetadatas);
|
||||
expect(axis.options).toBe(oldInstance);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/elements/PlotTelemetryFormatter"],
|
||||
function (PlotTelemetryFormatter) {
|
||||
'use strict';
|
||||
|
||||
describe("The PlotTelemetryFormatter", function () {
|
||||
var mockFormatter,
|
||||
formatter;
|
||||
|
||||
beforeEach(function () {
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
'telemetryFormatter',
|
||||
['formatDomainValue', 'formatRangeValue']
|
||||
);
|
||||
formatter = new PlotTelemetryFormatter(mockFormatter);
|
||||
});
|
||||
|
||||
describe("using domain & range format keys", function () {
|
||||
var rangeFormat = "someRangeFormat",
|
||||
domainFormat = "someDomainFormat";
|
||||
|
||||
beforeEach(function () {
|
||||
formatter.setRangeFormat(rangeFormat);
|
||||
formatter.setDomainFormat(domainFormat);
|
||||
});
|
||||
|
||||
it("includes format in formatDomainValue calls", function () {
|
||||
mockFormatter.formatDomainValue.andReturn("formatted!");
|
||||
expect(formatter.formatDomainValue(12321))
|
||||
.toEqual("formatted!");
|
||||
expect(mockFormatter.formatDomainValue)
|
||||
.toHaveBeenCalledWith(12321, domainFormat);
|
||||
});
|
||||
|
||||
it("includes format in formatRangeValue calls", function () {
|
||||
mockFormatter.formatRangeValue.andReturn("formatted!");
|
||||
expect(formatter.formatRangeValue(12321))
|
||||
.toEqual("formatted!");
|
||||
expect(mockFormatter.formatRangeValue)
|
||||
.toHaveBeenCalledWith(12321, rangeFormat);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -14,6 +14,7 @@
|
||||
"elements/PlotPosition",
|
||||
"elements/PlotPreparer",
|
||||
"elements/PlotSeriesWindow",
|
||||
"elements/PlotTelemetryFormatter",
|
||||
"elements/PlotTickGenerator",
|
||||
"elements/PlotUpdater",
|
||||
"modes/PlotModeOptions",
|
||||
|
@ -54,7 +54,8 @@ define(
|
||||
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
||||
return {
|
||||
text: this.telemetryFormatter.formatDomainValue(
|
||||
datum[this.domainMetadata.key]
|
||||
datum[this.domainMetadata.key],
|
||||
this.domainMetadata.format
|
||||
)
|
||||
};
|
||||
};
|
||||
|
@ -37,7 +37,8 @@
|
||||
"services": [
|
||||
{
|
||||
"key": "telemetryFormatter",
|
||||
"implementation": "TelemetryFormatter.js"
|
||||
"implementation": "TelemetryFormatter.js",
|
||||
"depends": [ "formatService", "DEFAULT_TIME_FORMAT", "$log" ]
|
||||
},
|
||||
{
|
||||
"key": "telemetrySubscriber",
|
||||
@ -63,4 +64,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,64 @@ define(
|
||||
getRangeValue: ZERO
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides metadata about telemetry associated with a
|
||||
* given domain object.
|
||||
*
|
||||
* @typedef TelemetryMetadata
|
||||
* @property {string} source the machine-readable identifier for
|
||||
* the source of telemetry data for this object; used by
|
||||
* {@link TelemetryService} implementations to determine
|
||||
* whether or not they provide data for this object.
|
||||
* @property {string} key the machine-readable identifier for
|
||||
* telemetry data associated with this specific object,
|
||||
* within that `source`.
|
||||
* @property {TelemetryDomainMetadata[]} domains supported domain
|
||||
* options for telemetry data associated with this object,
|
||||
* to use in interpreting a {@link TelemetrySeries}
|
||||
* @property {TelemetryRangeMetadata[]} ranges supported range
|
||||
* options for telemetry data associated with this object,
|
||||
* to use in interpreting a {@link TelemetrySeries}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides metadata about range options within a telemetry series.
|
||||
* Range options describe distinct properties within any given datum
|
||||
* of a telemetry series; for instance, a telemetry series containing
|
||||
* both raw and uncalibrated values may provide separate ranges for
|
||||
* each.
|
||||
*
|
||||
* @typedef TelemetryRangeMetadata
|
||||
* @property {string} key machine-readable identifier for this range
|
||||
* @property {string} name human-readable name for this range
|
||||
* @property {string} [units] human-readable units for this range
|
||||
* @property {string} [format] data format for this range; usually,
|
||||
* one of `number`, or `string`. If `undefined`,
|
||||
* should presume to be a `number`. Custom formats
|
||||
* may be indicated here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides metadata about domain options within a telemetry series.
|
||||
* Domain options describe distinct properties within any given datum
|
||||
* of a telemtry series; for instance, a telemetry series containing
|
||||
* both spacecraft event time and earth received times may provide
|
||||
* separate domains for each.
|
||||
*
|
||||
* Domains are typically used to represent timestamps in a telemetry
|
||||
* series, but more generally may express any property which will
|
||||
* have unique values for each datum in a series. It is this property
|
||||
* which makes domains distinct from ranges, as it makes these values
|
||||
* appropriate and meaningful for use to sort and bound a series.
|
||||
*
|
||||
* @typedef TelemetryDomainMetadata
|
||||
* @property {string} key machine-readable identifier for this range
|
||||
* @property {string} name human-readable name for this range
|
||||
* @property {string} [system] machine-readable identifier for the
|
||||
* time/date system associated with this domain;
|
||||
* used by {@link DateService}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A telemetry capability provides a means of requesting telemetry
|
||||
* for a specific object, and for unwrapping the response (to get
|
||||
|
@ -22,14 +22,13 @@
|
||||
/*global define,moment*/
|
||||
|
||||
define(
|
||||
['moment'],
|
||||
function (moment) {
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
// Date format to use for domain values; in particular,
|
||||
// use day-of-year instead of month/day
|
||||
var DATE_FORMAT = "YYYY-DDD HH:mm:ss",
|
||||
VALUE_FORMAT_DIGITS = 3;
|
||||
var VALUE_FORMAT_DIGITS = 3;
|
||||
|
||||
/**
|
||||
* The TelemetryFormatter is responsible for formatting (as text
|
||||
@ -37,22 +36,33 @@ define(
|
||||
* the range (usually value) of a data series.
|
||||
* @memberof platform/telemetry
|
||||
* @constructor
|
||||
* @param {FormatService} formatService the service to user to format
|
||||
* domain values
|
||||
* @param {string} defaultFormatKey the format to request when no
|
||||
* format has been otherwise specified
|
||||
* @param $log Angular's `$log`, to log warnings
|
||||
*/
|
||||
function TelemetryFormatter() {
|
||||
function TelemetryFormatter(formatService, defaultFormatKey, $log) {
|
||||
this.formatService = formatService;
|
||||
this.defaultFormat = formatService.getFormat(defaultFormatKey);
|
||||
this.$log = $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a domain value.
|
||||
* @param {number} v the domain value; a timestamp
|
||||
* @param {number} v the domain value; usually, a timestamp
|
||||
* in milliseconds since start of 1970
|
||||
* @param {string} [key] the key which identifies the
|
||||
* domain; if unspecified or unknown, this will
|
||||
* be treated as a standard timestamp.
|
||||
* @param {string} [key] a key which identifies the format
|
||||
* to use
|
||||
* @returns {string} a textual representation of the
|
||||
* data and time, suitable for display.
|
||||
*/
|
||||
TelemetryFormatter.prototype.formatDomainValue = function (v, key) {
|
||||
return isNaN(v) ? "" : moment.utc(v).format(DATE_FORMAT);
|
||||
var formatter = (key === undefined) ?
|
||||
this.defaultFormat :
|
||||
this.formatService.getFormat(key);
|
||||
|
||||
return isNaN(v) ? "" : formatter ? formatter.format(v) : String(v);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,16 +27,35 @@ define(
|
||||
"use strict";
|
||||
|
||||
describe("The telemetry formatter", function () {
|
||||
var formatter;
|
||||
var mockFormatService,
|
||||
mockFormat,
|
||||
formatter;
|
||||
|
||||
beforeEach(function () {
|
||||
formatter = new TelemetryFormatter();
|
||||
mockFormatService =
|
||||
jasmine.createSpyObj("formatService", ["getFormat"]);
|
||||
mockFormat = jasmine.createSpyObj("format", [
|
||||
"validate",
|
||||
"parse",
|
||||
"format"
|
||||
]);
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
formatter = new TelemetryFormatter(mockFormatService);
|
||||
});
|
||||
|
||||
it("formats domains using YYYY-DDD style", function () {
|
||||
expect(formatter.formatDomainValue(402513731000)).toEqual(
|
||||
"1982-276 17:22:11"
|
||||
);
|
||||
it("formats domains using the formatService", function () {
|
||||
var testValue = 12321, testResult = "some result";
|
||||
mockFormat.format.andReturn(testResult);
|
||||
|
||||
expect(formatter.formatDomainValue(testValue))
|
||||
.toEqual(testResult);
|
||||
expect(mockFormat.format).toHaveBeenCalledWith(testValue);
|
||||
});
|
||||
|
||||
it("passes format keys to the formatService", function () {
|
||||
formatter.formatDomainValue(12321, "someKey");
|
||||
expect(mockFormatService.getFormat)
|
||||
.toHaveBeenCalledWith("someKey");
|
||||
});
|
||||
|
||||
it("formats ranges as values", function () {
|
||||
@ -44,4 +63,4 @@ define(
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user