mirror of
https://github.com/nasa/openmct.git
synced 2025-06-19 15:43:48 +00:00
Merge remote-tracking branch 'github-open/open182' into open-master
This commit is contained in:
@ -6,6 +6,7 @@
|
|||||||
"platform/commonUI/browse",
|
"platform/commonUI/browse",
|
||||||
"platform/commonUI/edit",
|
"platform/commonUI/edit",
|
||||||
"platform/commonUI/dialog",
|
"platform/commonUI/dialog",
|
||||||
|
"platform/commonUI/formats",
|
||||||
"platform/commonUI/general",
|
"platform/commonUI/general",
|
||||||
"platform/commonUI/inspect",
|
"platform/commonUI/inspect",
|
||||||
"platform/commonUI/mobile",
|
"platform/commonUI/mobile",
|
||||||
|
@ -16,6 +16,23 @@
|
|||||||
"implementation": "SinewaveLimitCapability.js"
|
"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": [
|
"types": [
|
||||||
{
|
{
|
||||||
"key": "generator",
|
"key": "generator",
|
||||||
@ -38,6 +55,11 @@
|
|||||||
{
|
{
|
||||||
"key": "yesterday",
|
"key": "yesterday",
|
||||||
"name": "Yesterday"
|
"name": "Yesterday"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "delta",
|
||||||
|
"name": "Delta",
|
||||||
|
"format": "example.delta"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ranges": [
|
"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.
|
* Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
[],
|
['./SinewaveConstants'],
|
||||||
function () {
|
function (SinewaveConstants) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ONE_DAY = 60 * 60 * 24,
|
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) {
|
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 -
|
return (i + offset) * 1000 + firstTime * 1000 -
|
||||||
(domain === 'yesterday' ? ONE_DAY : 0);
|
(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[]" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formats": [
|
||||||
|
{
|
||||||
|
"key": "utc",
|
||||||
|
"implementation": "UTCTimeFormat.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "DEFAULT_TIME_FORMAT",
|
||||||
|
"value": "utc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
114
platform/commonUI/formats/src/FormatProvider.js
Normal file
114
platform/commonUI/formats/src/FormatProvider.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
* @method getFormat
|
||||||
|
* @memberof FormatService#
|
||||||
|
* @param {string} key the identifier for this format
|
||||||
|
* @returns {Format} the format
|
||||||
|
* @throws {Error} errors when the requested format is unrecognized
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
var formatMap = {};
|
||||||
|
|
||||||
|
function addToMap(Format) {
|
||||||
|
var key = Format.key;
|
||||||
|
if (key && !formatMap[key]) {
|
||||||
|
formatMap[key] = new Format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formats.forEach(addToMap);
|
||||||
|
this.formatMap = formatMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatProvider.prototype.getFormat = function (key) {
|
||||||
|
var format = this.formatMap[key];
|
||||||
|
if (!format) {
|
||||||
|
throw new Error("FormatProvider: 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;
|
||||||
|
});
|
68
platform/commonUI/formats/test/FormatProviderSpec.js
Normal file
68
platform/commonUI/formats/test/FormatProviderSpec.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,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' ]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Return constructors
|
||||||
|
mockFormats = KEYS.map(function (k, i) {
|
||||||
|
function MockFormat() { return mockFormatInstances[i]; }
|
||||||
|
MockFormat.key = k;
|
||||||
|
return MockFormat;
|
||||||
|
});
|
||||||
|
provider = new FormatProvider(mockFormats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up formats by key", function () {
|
||||||
|
KEYS.forEach(function (k, i) {
|
||||||
|
expect(provider.getFormat(k))
|
||||||
|
.toEqual(mockFormatInstances[i]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error about unknown formats", function () {
|
||||||
|
expect(function () {
|
||||||
|
provider.getFormat('some-unknown-format');
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
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"
|
||||||
|
]
|
@ -61,13 +61,18 @@
|
|||||||
{
|
{
|
||||||
"key": "TimeRangeController",
|
"key": "TimeRangeController",
|
||||||
"implementation": "controllers/TimeRangeController.js",
|
"implementation": "controllers/TimeRangeController.js",
|
||||||
"depends": [ "$scope", "now" ]
|
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT", "now" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "DateTimePickerController",
|
"key": "DateTimePickerController",
|
||||||
"implementation": "controllers/DateTimePickerController.js",
|
"implementation": "controllers/DateTimePickerController.js",
|
||||||
"depends": [ "$scope", "now" ]
|
"depends": [ "$scope", "now" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "DateTimeFieldController",
|
||||||
|
"implementation": "controllers/DateTimeFieldController.js",
|
||||||
|
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "TreeNodeController",
|
"key": "TreeNodeController",
|
||||||
"implementation": "controllers/TreeNodeController.js",
|
"implementation": "controllers/TreeNodeController.js",
|
||||||
@ -255,6 +260,10 @@
|
|||||||
{
|
{
|
||||||
"key": "datetime-picker",
|
"key": "datetime-picker",
|
||||||
"templateUrl": "templates/controls/datetime-picker.html"
|
"templateUrl": "templates/controls/datetime-picker.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "datetime-field",
|
||||||
|
"templateUrl": "templates/controls/datetime-field.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"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 ng-controller="TimeRangeController">
|
||||||
<div class="l-time-range-inputs-holder">
|
<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-inputs-elem ui-symbol type-icon">C</span>
|
||||||
<span class="l-time-range-input" ng-controller="ToggleController as t1">
|
<span class="l-time-range-input">
|
||||||
<!--<span class="lbl">Start</span>-->
|
<mct-control key="'datetime-field'"
|
||||||
<span class="s-btn time-range-start">
|
structure="{ format: parameters.format }"
|
||||||
<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"
|
ng-model="ngModel.outer"
|
||||||
field="'start'"
|
field="'start'"
|
||||||
options="{ hours: true }">
|
class="time-range-start">
|
||||||
</mct-control>
|
</mct-control>
|
||||||
</div>
|
|
||||||
</mct-popup>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="l-time-range-inputs-elem lbl">to</span>
|
<span class="l-time-range-inputs-elem lbl">to</span>
|
||||||
|
|
||||||
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||||
<!--<span class="lbl">End</span>-->
|
<mct-control key="'datetime-field'"
|
||||||
<span class="s-btn l-time-range-input">
|
structure="{ format: parameters.format }"
|
||||||
<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"
|
ng-model="ngModel.outer"
|
||||||
field="'end'"
|
field="'end'"
|
||||||
options="{ hours: true }">
|
class="time-range-end">
|
||||||
</mct-control>
|
</mct-control>
|
||||||
</div>
|
|
||||||
</mct-popup>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -97,7 +74,7 @@
|
|||||||
<div class="l-time-range-ticks-holder">
|
<div class="l-time-range-ticks-holder">
|
||||||
<div class="l-time-range-ticks">
|
<div class="l-time-range-ticks">
|
||||||
<div
|
<div
|
||||||
ng-repeat="tick in ticks"
|
ng-repeat="tick in ticks track by $index"
|
||||||
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
||||||
class="tick tick-x"
|
class="tick tick-x"
|
||||||
>
|
>
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
updateFromModel($scope.ngModel[$scope.field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('structure.format', setFormat);
|
||||||
|
$scope.$watch('ngModel[field]', updateFromModel);
|
||||||
|
$scope.$watch('textValue', updateFromView);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimeFieldController;
|
||||||
|
}
|
||||||
|
);
|
@ -26,33 +26,32 @@ define(
|
|||||||
function (moment) {
|
function (moment) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
var TICK_SPACING_PX = 150;
|
||||||
TICK_SPACING_PX = 150;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Controller used by the `time-controller` template.
|
||||||
* @memberof platform/commonUI/general
|
* @memberof platform/commonUI/general
|
||||||
* @constructor
|
* @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,
|
var tickCount = 2,
|
||||||
innerMinimumSpan = 1000, // 1 second
|
innerMinimumSpan = 1000, // 1 second
|
||||||
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
|
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
|
||||||
initialDragValue;
|
initialDragValue,
|
||||||
|
formatter = formatService.getFormat(defaultFormat);
|
||||||
|
|
||||||
function formatTimestamp(ts) {
|
function formatTimestamp(ts) {
|
||||||
return moment.utc(ts).format(DATE_FORMAT);
|
return formatter.format(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTimestamp(text) {
|
// From 0.0-1.0 to "0%"-"100%"
|
||||||
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%"
|
|
||||||
function toPercent(p) {
|
function toPercent(p) {
|
||||||
return (100 * p) + "%";
|
return (100 * p) + "%";
|
||||||
}
|
}
|
||||||
@ -101,41 +100,15 @@ define(
|
|||||||
return { start: bounds.start, end: bounds.end };
|
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) {
|
function updateViewFromModel(ngModel) {
|
||||||
var t = now();
|
|
||||||
|
|
||||||
ngModel = ngModel || {};
|
ngModel = ngModel || {};
|
||||||
ngModel.outer = ngModel.outer || defaultBounds();
|
ngModel.outer = ngModel.outer || defaultBounds();
|
||||||
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
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)
|
// Stick it back is scope (in case we just set defaults)
|
||||||
$scope.ngModel = ngModel;
|
$scope.ngModel = ngModel;
|
||||||
|
|
||||||
|
updateViewForInnerSpanFromModel(ngModel);
|
||||||
updateTicks();
|
updateTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +128,8 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toMillis(pixels) {
|
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;
|
return (pixels / $scope.spanWidth) * span;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,36 +217,10 @@ define(
|
|||||||
updateTicks();
|
updateTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStartFromText(value) {
|
function updateFormat(key) {
|
||||||
try {
|
formatter = formatService.getFormat(key || defaultFormat);
|
||||||
updateOuterStart(parseTimestamp(value));
|
updateViewForInnerSpanFromModel($scope.ngModel);
|
||||||
updateBoundsTextForProperty($scope.ngModel, 'end');
|
updateTicks();
|
||||||
$scope.boundsModel.startValid = true;
|
|
||||||
} catch (e) {
|
|
||||||
$scope.boundsModel.startValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.startLeftDrag = startLeftDrag;
|
$scope.startLeftDrag = startLeftDrag;
|
||||||
@ -282,21 +230,18 @@ define(
|
|||||||
$scope.rightDrag = rightDrag;
|
$scope.rightDrag = rightDrag;
|
||||||
$scope.middleDrag = middleDrag;
|
$scope.middleDrag = middleDrag;
|
||||||
|
|
||||||
$scope.state = false;
|
|
||||||
$scope.ticks = [];
|
$scope.ticks = [];
|
||||||
$scope.boundsModel = {};
|
|
||||||
|
|
||||||
// Initialize scope to defaults
|
// Initialize scope to defaults
|
||||||
updateViewFromModel($scope.ngModel);
|
updateViewFromModel($scope.ngModel);
|
||||||
|
|
||||||
$scope.$watchCollection("ngModel", updateViewFromModel);
|
$scope.$watchCollection("ngModel", updateViewFromModel);
|
||||||
$scope.$watch("spanWidth", updateSpanWidth);
|
$scope.$watch("spanWidth", updateSpanWidth);
|
||||||
$scope.$watch("ngModel.outer.start", updateStartFromPicker);
|
$scope.$watch("ngModel.outer.start", updateOuterStart);
|
||||||
$scope.$watch("ngModel.outer.end", updateEndFromPicker);
|
$scope.$watch("ngModel.outer.end", updateOuterEnd);
|
||||||
$scope.$watch("boundsModel.start", updateStartFromText);
|
$scope.$watch("parameters.format", updateFormat);
|
||||||
$scope.$watch("boundsModel.end", updateEndFromText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 () {
|
describe("The TimeRangeController", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
|
mockFormatService,
|
||||||
|
testDefaultFormat,
|
||||||
mockNow,
|
mockNow,
|
||||||
|
mockFormat,
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
function fireWatch(expr, value) {
|
function fireWatch(expr, value) {
|
||||||
@ -57,8 +60,30 @@ define(
|
|||||||
"$scope",
|
"$scope",
|
||||||
[ "$apply", "$watch", "$watchCollection" ]
|
[ "$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');
|
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 () {
|
it("watches the model that was passed in", function () {
|
||||||
@ -167,70 +192,22 @@ define(
|
|||||||
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
.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 () {
|
it("watches for changes in format selection", function () {
|
||||||
var newStart = "Not a date",
|
expect(mockFormatService.getFormat)
|
||||||
newEnd = "Definitely not a date",
|
.not.toHaveBeenCalledWith('test-format');
|
||||||
oldStart = mockScope.ngModel.outer.start,
|
fireWatch("parameters.format", 'test-format');
|
||||||
oldEnd = mockScope.ngModel.outer.end;
|
expect(mockFormatService.getFormat)
|
||||||
|
.toHaveBeenCalledWith('test-format');
|
||||||
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 () {
|
it("throws an error for unknown formats", function () {
|
||||||
// Don't want the controller "fixing" bad or
|
mockFormatService.getFormat.andReturn(undefined);
|
||||||
// irregularly-formatted input out from under
|
expect(function () {
|
||||||
// the user's fingertips.
|
fireWatch("parameters.format", "some-format");
|
||||||
var newStart = "Not a date",
|
}).toThrow();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"controllers/BottomBarController",
|
"controllers/BottomBarController",
|
||||||
"controllers/ClickAwayController",
|
"controllers/ClickAwayController",
|
||||||
"controllers/ContextMenuController",
|
"controllers/ContextMenuController",
|
||||||
|
"controllers/DateTimeFieldController",
|
||||||
"controllers/DateTimePickerController",
|
"controllers/DateTimePickerController",
|
||||||
"controllers/GetterSetterController",
|
"controllers/GetterSetterController",
|
||||||
"controllers/SelectorController",
|
"controllers/SelectorController",
|
||||||
|
@ -36,9 +36,9 @@
|
|||||||
{
|
{
|
||||||
"key": "TIME_CONDUCTOR_DOMAINS",
|
"key": "TIME_CONDUCTOR_DOMAINS",
|
||||||
"value": [
|
"value": [
|
||||||
{ "key": "time", "name": "Time" },
|
{ "key": "time", "name": "UTC", "format": "utc" }
|
||||||
{ "key": "yesterday", "name": "Yesterday" }
|
|
||||||
],
|
],
|
||||||
|
"priority": "fallback",
|
||||||
"comment": "Placeholder; to be replaced by inspection of available domains."
|
"comment": "Placeholder; to be replaced by inspection of available domains."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<mct-include key="'time-controller'"
|
<mct-include key="'time-controller'"
|
||||||
|
parameters='parameters'
|
||||||
ng-model='ngModel.conductor'>
|
ng-model='ngModel.conductor'>
|
||||||
</mct-include>
|
</mct-include>
|
||||||
<mct-control key="'select'"
|
<mct-control key="'select'"
|
||||||
|
@ -27,7 +27,10 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var TEMPLATE = [
|
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>"
|
"</mct-include>"
|
||||||
].join(''),
|
].join(''),
|
||||||
THROTTLE_MS = 200,
|
THROTTLE_MS = 200,
|
||||||
@ -74,11 +77,11 @@ define(
|
|||||||
broadcastBounds;
|
broadcastBounds;
|
||||||
|
|
||||||
// Combine start/end times into a single object
|
// Combine start/end times into a single object
|
||||||
function bounds(start, end) {
|
function bounds() {
|
||||||
return {
|
return {
|
||||||
start: conductor.displayStart(),
|
start: conductor.displayStart(),
|
||||||
end: conductor.displayEnd(),
|
end: conductor.displayEnd(),
|
||||||
domain: conductor.domain()
|
domain: conductor.domain().key
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +100,9 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDomain(value) {
|
function updateDomain(value) {
|
||||||
conductor.domain(value);
|
var newDomain = conductor.domain(value);
|
||||||
repScope.$broadcast('telemetry:display:bounds', bounds(
|
conductorScope.parameters.format = newDomain.format;
|
||||||
conductor.displayStart(),
|
repScope.$broadcast('telemetry:display:bounds', bounds());
|
||||||
conductor.displayEnd(),
|
|
||||||
conductor.domain()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// telemetry domain metadata -> option for a select control
|
// telemetry domain metadata -> option for a select control
|
||||||
@ -130,7 +130,8 @@ define(
|
|||||||
{ outer: bounds(), inner: bounds() };
|
{ outer: bounds(), inner: bounds() };
|
||||||
conductorScope.ngModel.options =
|
conductorScope.ngModel.options =
|
||||||
conductor.domainOptions().map(makeOption);
|
conductor.domainOptions().map(makeOption);
|
||||||
conductorScope.ngModel.domain = conductor.domain();
|
conductorScope.ngModel.domain = conductor.domain().key;
|
||||||
|
conductorScope.parameters = {};
|
||||||
|
|
||||||
conductorScope
|
conductorScope
|
||||||
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
||||||
|
@ -51,7 +51,7 @@ define(
|
|||||||
request = request || {};
|
request = request || {};
|
||||||
request.start = start;
|
request.start = start;
|
||||||
request.end = end;
|
request.end = end;
|
||||||
request.domain = domain;
|
request.domain = domain.key;
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ define(
|
|||||||
function TimeConductor(start, end, domains) {
|
function TimeConductor(start, end, domains) {
|
||||||
this.range = { start: start, end: end };
|
this.range = { start: start, end: end };
|
||||||
this.domains = domains;
|
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
|
* Get available domain options which can be used to bound time
|
||||||
* selection.
|
* selection.
|
||||||
* @returns {TelemetryDomain[]} available domains
|
* @returns {TelemetryDomainMetadata[]} available domains
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.domainOptions = function () {
|
TimeConductor.prototype.domainOptions = function () {
|
||||||
return this.domains;
|
return this.domains;
|
||||||
@ -82,19 +82,21 @@ define(
|
|||||||
/**
|
/**
|
||||||
* Get or set (if called with an argument) the active domain.
|
* Get or set (if called with an argument) the active domain.
|
||||||
* @param {string} [key] the key identifying the domain choice
|
* @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) {
|
TimeConductor.prototype.domain = function (key) {
|
||||||
function matchesKey(domain) {
|
var i;
|
||||||
return domain.key === key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
if (!this.domains.some(matchesKey)) {
|
for (i = 0; i < this.domains.length; i += 1) {
|
||||||
|
if (this.domains[i].key === key) {
|
||||||
|
return (this.activeDomain = this.domains[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error("Unknown domain " + key);
|
throw new Error("Unknown domain " + key);
|
||||||
}
|
}
|
||||||
this.activeDomain = key;
|
|
||||||
}
|
|
||||||
return this.activeDomain;
|
return this.activeDomain;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ define(
|
|||||||
it("exposes conductor state in scope", function () {
|
it("exposes conductor state in scope", function () {
|
||||||
mockConductor.displayStart.andReturn(1977);
|
mockConductor.displayStart.andReturn(1977);
|
||||||
mockConductor.displayEnd.andReturn(1984);
|
mockConductor.displayEnd.andReturn(1984);
|
||||||
mockConductor.domain.andReturn('d');
|
mockConductor.domain.andReturn({ key: 'd' });
|
||||||
representer.represent(testViews[0], {});
|
representer.represent(testViews[0], {});
|
||||||
|
|
||||||
expect(mockNewScope.ngModel.conductor).toEqual({
|
expect(mockNewScope.ngModel.conductor).toEqual({
|
||||||
@ -219,7 +219,7 @@ define(
|
|||||||
representer.represent(testViews[0], null);
|
representer.represent(testViews[0], null);
|
||||||
|
|
||||||
expect(mockNewScope.ngModel.domain)
|
expect(mockNewScope.ngModel.domain)
|
||||||
.toEqual(mockConductor.domain());
|
.toEqual(mockConductor.domain().key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes domain options in scope", function () {
|
it("exposes domain options in scope", function () {
|
||||||
|
@ -77,7 +77,7 @@ define(
|
|||||||
|
|
||||||
mockConductor.displayStart.andReturn(42);
|
mockConductor.displayStart.andReturn(42);
|
||||||
mockConductor.displayEnd.andReturn(1977);
|
mockConductor.displayEnd.andReturn(1977);
|
||||||
mockConductor.domain.andReturn("testDomain");
|
mockConductor.domain.andReturn({ key: "testDomain" });
|
||||||
|
|
||||||
decorator = new ConductorTelemetryDecorator(
|
decorator = new ConductorTelemetryDecorator(
|
||||||
mockConductorService,
|
mockConductorService,
|
||||||
@ -104,7 +104,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("with domain selection", function () {
|
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 () {
|
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 () {
|
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 () {
|
it("allows the domain choice to be changed", function () {
|
||||||
conductor.domain(testDomains[1].key);
|
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 () {
|
it("throws an error on attempts to set an invalid domain", function () {
|
||||||
|
@ -31,10 +31,19 @@ define(
|
|||||||
"./elements/PlotPalette",
|
"./elements/PlotPalette",
|
||||||
"./elements/PlotAxis",
|
"./elements/PlotAxis",
|
||||||
"./elements/PlotLimitTracker",
|
"./elements/PlotLimitTracker",
|
||||||
|
"./elements/PlotTelemetryFormatter",
|
||||||
"./modes/PlotModeOptions",
|
"./modes/PlotModeOptions",
|
||||||
"./SubPlotFactory"
|
"./SubPlotFactory"
|
||||||
],
|
],
|
||||||
function (PlotUpdater, PlotPalette, PlotAxis, PlotLimitTracker, PlotModeOptions, SubPlotFactory) {
|
function (
|
||||||
|
PlotUpdater,
|
||||||
|
PlotPalette,
|
||||||
|
PlotAxis,
|
||||||
|
PlotLimitTracker,
|
||||||
|
PlotTelemetryFormatter,
|
||||||
|
PlotModeOptions,
|
||||||
|
SubPlotFactory
|
||||||
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var AXIS_DEFAULTS = [
|
var AXIS_DEFAULTS = [
|
||||||
@ -62,7 +71,10 @@ define(
|
|||||||
PLOT_FIXED_DURATION
|
PLOT_FIXED_DURATION
|
||||||
) {
|
) {
|
||||||
var self = this,
|
var self = this,
|
||||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
plotTelemetryFormatter =
|
||||||
|
new PlotTelemetryFormatter(telemetryFormatter),
|
||||||
|
subPlotFactory =
|
||||||
|
new SubPlotFactory(plotTelemetryFormatter),
|
||||||
cachedObjects = [],
|
cachedObjects = [],
|
||||||
updater,
|
updater,
|
||||||
lastBounds,
|
lastBounds,
|
||||||
@ -71,10 +83,9 @@ define(
|
|||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
// available for each axis.)
|
// available for each axis.)
|
||||||
function setupAxes(metadatas) {
|
function setupAxes(metadatas) {
|
||||||
$scope.axes = [
|
$scope.axes.forEach(function (axis) {
|
||||||
new PlotAxis("domain", metadatas, AXIS_DEFAULTS[0]),
|
axis.updateMetadata(metadatas);
|
||||||
new PlotAxis("range", metadatas, AXIS_DEFAULTS[1])
|
});
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger an update of a specific subplot;
|
// 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
|
// Handle new telemetry data in this plot
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
self.pending = false;
|
self.pending = false;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
setupModes(handle.getTelemetryObjects());
|
setupModes(handle.getTelemetryObjects());
|
||||||
}
|
setupAxes(handle.getMetadata());
|
||||||
if (updater) {
|
getUpdater().update();
|
||||||
updater.update();
|
|
||||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
}
|
|
||||||
if (self.limitTracker) {
|
|
||||||
self.limitTracker.update();
|
self.limitTracker.update();
|
||||||
}
|
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Display new historical data as it becomes available
|
// Display new historical data as it becomes available
|
||||||
function addHistoricalData(domainObject, series) {
|
function addHistoricalData(domainObject, series) {
|
||||||
self.pending = false;
|
self.pending = false;
|
||||||
updater.addHistorical(domainObject, series);
|
getUpdater().addHistorical(domainObject, series);
|
||||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a new request for historical telemetry
|
// Issue a new request for historical telemetry
|
||||||
function requestTelemetry() {
|
function requestTelemetry() {
|
||||||
if (handle && updater) {
|
if (handle) {
|
||||||
handle.request({}, addHistoricalData);
|
handle.request({}, addHistoricalData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Requery for data entirely
|
||||||
|
function replot() {
|
||||||
|
if (handle) {
|
||||||
|
updater = undefined;
|
||||||
|
requestTelemetry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new subscription; telemetrySubscriber gets
|
// Create a new subscription; telemetrySubscriber gets
|
||||||
// to do the meaningful work here.
|
// to do the meaningful work here.
|
||||||
function subscribe(domainObject) {
|
function subscribe(domainObject) {
|
||||||
@ -167,12 +190,7 @@ define(
|
|||||||
updateValues,
|
updateValues,
|
||||||
true // Lossless
|
true // Lossless
|
||||||
);
|
);
|
||||||
if (handle) {
|
replot();
|
||||||
setupModes(handle.getTelemetryObjects());
|
|
||||||
setupAxes(handle.getMetadata());
|
|
||||||
recreateUpdater();
|
|
||||||
requestTelemetry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the current subscription (called when scope is destroyed)
|
// Release the current subscription (called when scope is destroyed)
|
||||||
@ -185,12 +203,22 @@ define(
|
|||||||
|
|
||||||
// Respond to a display bounds change (requery for data)
|
// Respond to a display bounds change (requery for data)
|
||||||
function changeDisplayBounds(event, bounds) {
|
function changeDisplayBounds(event, bounds) {
|
||||||
|
var domainAxis = $scope.axes[0];
|
||||||
|
|
||||||
|
domainAxis.chooseOption(bounds.domain);
|
||||||
|
plotTelemetryFormatter
|
||||||
|
.setDomainFormat(domainAxis.active.format);
|
||||||
|
|
||||||
self.pending = true;
|
self.pending = true;
|
||||||
releaseSubscription();
|
releaseSubscription();
|
||||||
subscribe($scope.domainObject);
|
subscribe($scope.domainObject);
|
||||||
setBasePanZoom(bounds);
|
setBasePanZoom(bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDomainFormat(format) {
|
||||||
|
plotTelemetryFormatter.setDomainFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||||
this.updateValues = updateValues;
|
this.updateValues = updateValues;
|
||||||
|
|
||||||
@ -202,6 +230,13 @@ define(
|
|||||||
|
|
||||||
self.pending = true;
|
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
|
// Subscribe to telemetry when a domain object becomes available
|
||||||
$scope.$watch('domainObject', subscribe);
|
$scope.$watch('domainObject', subscribe);
|
||||||
|
|
||||||
|
@ -121,9 +121,9 @@ define(
|
|||||||
// Utility, for map/forEach loops. Index 0 is domain,
|
// Utility, for map/forEach loops. Index 0 is domain,
|
||||||
// index 1 is range.
|
// index 1 is range.
|
||||||
function formatValue(v, i) {
|
function formatValue(v, i) {
|
||||||
return (i ?
|
return i ?
|
||||||
formatter.formatRangeValue :
|
formatter.formatRangeValue(v) :
|
||||||
formatter.formatDomainValue)(v);
|
formatter.formatDomainValue(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hoverCoordinates = this.mousePosition &&
|
this.hoverCoordinates = this.mousePosition &&
|
||||||
|
@ -46,21 +46,9 @@ define(
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function PlotAxis(axisType, metadatas, defaultValue) {
|
function PlotAxis(axisType, metadatas, defaultValue) {
|
||||||
var keys = {},
|
this.axisType = axisType;
|
||||||
options = [];
|
this.defaultValue = defaultValue;
|
||||||
|
this.optionKeys = {};
|
||||||
// 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently chosen option for this axis. An
|
* The currently chosen option for this axis. An
|
||||||
@ -68,7 +56,7 @@ define(
|
|||||||
* directly form the plot template.
|
* directly form the plot template.
|
||||||
* @memberof platform/features/plot.PlotAxis#
|
* @memberof platform/features/plot.PlotAxis#
|
||||||
*/
|
*/
|
||||||
this.active = options[0] || defaultValue;
|
this.active = defaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of options applicable for this axis;
|
* The set of options applicable for this axis;
|
||||||
@ -77,9 +65,71 @@ define(
|
|||||||
* human-readable names respectively)
|
* human-readable names respectively)
|
||||||
* @memberof platform/features/plot.PlotAxis#
|
* @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;
|
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;
|
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
|
// Generate ticks; interpolate from start up to
|
||||||
// start + span in count steps, using the provided
|
// start + span in count steps, using the provided
|
||||||
// formatter to represent each value.
|
// formatter to represent each value.
|
||||||
@ -72,7 +80,7 @@ define(
|
|||||||
panZoom.origin[0],
|
panZoom.origin[0],
|
||||||
panZoom.dimensions[0],
|
panZoom.dimensions[0],
|
||||||
count,
|
count,
|
||||||
this.formatter.formatDomainValue
|
bind(this.formatter.formatDomainValue, this.formatter)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +95,7 @@ define(
|
|||||||
panZoom.origin[1],
|
panZoom.origin[1],
|
||||||
panZoom.dimensions[1],
|
panZoom.dimensions[1],
|
||||||
count,
|
count,
|
||||||
this.formatter.formatRangeValue
|
bind(this.formatter.formatRangeValue, this.formatter)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,8 +169,9 @@ define(
|
|||||||
mockDomainObject
|
mockDomainObject
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make an object available
|
// Make an object available; invoke handler's callback
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
expect(controller.getModeOptions().length).toEqual(1);
|
expect(controller.getModeOptions().length).toEqual(1);
|
||||||
|
|
||||||
@ -181,8 +182,9 @@ define(
|
|||||||
mockDomainObject
|
mockDomainObject
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make an object available
|
// Make an object available; invoke handler's callback
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
expect(controller.getModeOptions().length).toEqual(2);
|
expect(controller.getModeOptions().length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,12 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe("A plot axis", function () {
|
describe("A plot axis", function () {
|
||||||
var testMetadatas = [
|
var testMetadatas,
|
||||||
|
testDefault,
|
||||||
|
axis;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testMetadatas = [
|
||||||
{
|
{
|
||||||
tests: [
|
tests: [
|
||||||
{ key: "t0", name: "T0" },
|
{ key: "t0", name: "T0" },
|
||||||
@ -52,13 +57,14 @@ define(
|
|||||||
{ key: "t6", name: "T6" }
|
{ key: "t6", name: "T6" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
testDefault = { key: "test", name: "Test" },
|
testDefault = { key: "test", name: "Test" };
|
||||||
controller = new PlotAxis("tests", testMetadatas, testDefault);
|
axis = new PlotAxis("tests", testMetadatas, testDefault);
|
||||||
|
});
|
||||||
|
|
||||||
it("pulls out a list of domain or range options", function () {
|
it("pulls out a list of domain or range options", function () {
|
||||||
// Should have filtered out duplicates, etc
|
// Should have filtered out duplicates, etc
|
||||||
expect(controller.options).toEqual([
|
expect(axis.options).toEqual([
|
||||||
{ key: "t0", name: "T0" },
|
{ key: "t0", name: "T0" },
|
||||||
{ key: "t1", name: "T1" },
|
{ key: "t1", name: "T1" },
|
||||||
{ key: "t2", name: "T2" },
|
{ key: "t2", name: "T2" },
|
||||||
@ -70,7 +76,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("chooses the first option as a default", function () {
|
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 () {
|
it("falls back to a provided default if no options are present", function () {
|
||||||
@ -78,6 +84,26 @@ define(
|
|||||||
.toEqual(testDefault);
|
.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/PlotPosition",
|
||||||
"elements/PlotPreparer",
|
"elements/PlotPreparer",
|
||||||
"elements/PlotSeriesWindow",
|
"elements/PlotSeriesWindow",
|
||||||
|
"elements/PlotTelemetryFormatter",
|
||||||
"elements/PlotTickGenerator",
|
"elements/PlotTickGenerator",
|
||||||
"elements/PlotUpdater",
|
"elements/PlotUpdater",
|
||||||
"modes/PlotModeOptions",
|
"modes/PlotModeOptions",
|
||||||
|
@ -54,7 +54,8 @@ define(
|
|||||||
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
||||||
return {
|
return {
|
||||||
text: this.telemetryFormatter.formatDomainValue(
|
text: this.telemetryFormatter.formatDomainValue(
|
||||||
datum[this.domainMetadata.key]
|
datum[this.domainMetadata.key],
|
||||||
|
this.domainMetadata.format
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "telemetryFormatter",
|
"key": "telemetryFormatter",
|
||||||
"implementation": "TelemetryFormatter.js"
|
"implementation": "TelemetryFormatter.js",
|
||||||
|
"depends": [ "formatService", "DEFAULT_TIME_FORMAT" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "telemetrySubscriber",
|
"key": "telemetrySubscriber",
|
||||||
|
@ -36,6 +36,64 @@ define(
|
|||||||
getRangeValue: ZERO
|
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
|
* A telemetry capability provides a means of requesting telemetry
|
||||||
* for a specific object, and for unwrapping the response (to get
|
* for a specific object, and for unwrapping the response (to get
|
||||||
|
@ -22,14 +22,13 @@
|
|||||||
/*global define,moment*/
|
/*global define,moment*/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
['moment'],
|
[],
|
||||||
function (moment) {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Date format to use for domain values; in particular,
|
// Date format to use for domain values; in particular,
|
||||||
// use day-of-year instead of month/day
|
// use day-of-year instead of month/day
|
||||||
var DATE_FORMAT = "YYYY-DDD HH:mm:ss",
|
var VALUE_FORMAT_DIGITS = 3;
|
||||||
VALUE_FORMAT_DIGITS = 3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TelemetryFormatter is responsible for formatting (as text
|
* The TelemetryFormatter is responsible for formatting (as text
|
||||||
@ -37,22 +36,31 @@ define(
|
|||||||
* the range (usually value) of a data series.
|
* the range (usually value) of a data series.
|
||||||
* @memberof platform/telemetry
|
* @memberof platform/telemetry
|
||||||
* @constructor
|
* @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
|
||||||
*/
|
*/
|
||||||
function TelemetryFormatter() {
|
function TelemetryFormatter(formatService, defaultFormatKey) {
|
||||||
|
this.formatService = formatService;
|
||||||
|
this.defaultFormat = formatService.getFormat(defaultFormatKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a domain value.
|
* 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
|
* in milliseconds since start of 1970
|
||||||
* @param {string} [key] the key which identifies the
|
* @param {string} [key] a key which identifies the format
|
||||||
* domain; if unspecified or unknown, this will
|
* to use
|
||||||
* be treated as a standard timestamp.
|
|
||||||
* @returns {string} a textual representation of the
|
* @returns {string} a textual representation of the
|
||||||
* data and time, suitable for display.
|
* data and time, suitable for display.
|
||||||
*/
|
*/
|
||||||
TelemetryFormatter.prototype.formatDomainValue = function (v, key) {
|
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.format(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,16 +27,35 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe("The telemetry formatter", function () {
|
describe("The telemetry formatter", function () {
|
||||||
var formatter;
|
var mockFormatService,
|
||||||
|
mockFormat,
|
||||||
|
formatter;
|
||||||
|
|
||||||
beforeEach(function () {
|
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 () {
|
it("formats domains using the formatService", function () {
|
||||||
expect(formatter.formatDomainValue(402513731000)).toEqual(
|
var testValue = 12321, testResult = "some result";
|
||||||
"1982-276 17:22:11"
|
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 () {
|
it("formats ranges as values", function () {
|
||||||
|
Reference in New Issue
Block a user