From e87338965555918775ba3f5695989a19e1d2f4ec Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 2 Sep 2015 15:57:52 -0700 Subject: [PATCH 01/78] [Conductor] Add time conductor widget Add widget for the time conductor using a representer, WTD-1515. --- bundles.json | 1 + platform/features/conductor/README.md | 3 + platform/features/conductor/bundle.json | 10 ++ .../conductor/src/ConductorRepresenter.js | 106 ++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 platform/features/conductor/README.md create mode 100644 platform/features/conductor/bundle.json create mode 100644 platform/features/conductor/src/ConductorRepresenter.js diff --git a/bundles.json b/bundles.json index 79ab2dd246..4018e453ee 100644 --- a/bundles.json +++ b/bundles.json @@ -17,6 +17,7 @@ "platform/features/plot", "platform/features/scrolling", "platform/features/events", + "platform/features/conductor", "platform/forms", "platform/identity", "platform/persistence/queue", diff --git a/platform/features/conductor/README.md b/platform/features/conductor/README.md new file mode 100644 index 0000000000..97c7dde3c2 --- /dev/null +++ b/platform/features/conductor/README.md @@ -0,0 +1,3 @@ +Provides the time conductor, a control which appears at the +bottom of the screen allowing telemetry start and end times +to be modified. diff --git a/platform/features/conductor/bundle.json b/platform/features/conductor/bundle.json new file mode 100644 index 0000000000..d9905b6f24 --- /dev/null +++ b/platform/features/conductor/bundle.json @@ -0,0 +1,10 @@ +{ + "extensions": { + "representers": [ + { + "implementation": "ConductorRepresenter.js", + "depends": [ "$compile", "views[]" ] + } + ] + } +} diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js new file mode 100644 index 0000000000..364cbe3296 --- /dev/null +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -0,0 +1,106 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + [], + function () { + "use strict"; + + var CONDUCTOR_HEIGHT = "100px", + TEMPLATE = [ + '
', + "", + '
' + ].join(''); + + /** + * The ConductorRepresenter attaches the universal time conductor + * to views. + * + * @memberof platform/commonUI/edit + * @implements {Representer} + * @constructor + */ + function ConductorRepresenter($compile, views, scope, element) { + var conductorScope; + + // Angular doesn't like objects to retain references to scope + this.getScope = function () { return scope; }; + this.conductorScope = function (s) { + return (conductorScope = arguments.length > 0 ? s : conductorScope); + }; + + this.element = element; + this.showing = false; + this.views = views; + this.$compile = $compile; + + this.originalHeight = element.css('height'); + this.hadAbs = element.hasClass('abs'); + } + + // Handle a specific representation of a specific domain object + ConductorRepresenter.prototype.represent = function represent(representation, representedObject) { + if (this.showing) { + this.destroy(); + } + + if (this.views.indexOf(representation) !== -1) { + // Create a new scope for the conductor + this.conductorScope(this.getScope().$new()); + this.conductorElement = + this.$compile(TEMPLATE)(this.conductorScope()); + this.element.after(this.conductorElement[0]); + this.element.addClass('abs'); + this.element.css('bottom', CONDUCTOR_HEIGHT); + this.showing = true; + } + }; + + // Respond to the destruction of the current representation. + ConductorRepresenter.prototype.destroy = function destroy() { + // Restore the original size of the mct-representation + if (!this.hadAbs) { + this.element.removeClass('abs'); + } + this.element.css('height', this.originalHeight); + + // ...and remove the conductor + if (this.conductorElement) { + this.conductorElement.remove(); + this.conductorElement = undefined; + } + + // Finally, destroy its scope + if (this.conductorScope()) { + this.conductorScope().$destroy(); + this.conductorScope(undefined); + } + + this.showing = false; + }; + + return ConductorRepresenter; + } +); + From 91fe3d798f61caa903905f95ade35bb69fab55ea Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 2 Sep 2015 16:31:58 -0700 Subject: [PATCH 02/78] [Time Conductor] Begin adding controller WTD-1515 --- platform/commonUI/general/bundle.json | 5 ++ .../templates/controls/time-controller.html | 78 +++++++++---------- .../controllers/TimeConductorController.js | 62 +++++++++++++++ 3 files changed, 102 insertions(+), 43 deletions(-) create mode 100644 platform/commonUI/general/src/controllers/TimeConductorController.js diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 33242ccdcf..cd239c7d08 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -57,6 +57,11 @@ } ], "controllers": [ + { + "key": "TimeConductorController", + "implementation": "controllers/TimeConductorController.js", + "depends": [ "$scope" ] + }, { "key": "TreeNodeController", "implementation": "controllers/TreeNodeController.js", diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index fe1b6ff14a..346b1b7143 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -22,48 +22,40 @@ properly on the range left and right bounds. --> -
+
+
+ Start: + End: +
-
-
- Start: - End: -
+
+
+
+
+
+
+
+
+
{{startInnerText}}
+
+
+
{{startOuterText}}
+
+
+
+
-
-
-
-
-
-
-
-
05/22 14:46
-
-
-
07/22 01:21
-
-
-
-
- -
-
-
- {{tick}} -
-
-
-
\ No newline at end of file +
+
+
+ {{tick}} +
+
+
+
diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js new file mode 100644 index 0000000000..a186651c0c --- /dev/null +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -0,0 +1,62 @@ +/***************************************************************************** + * 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"; + + /** + * A ToggleController is used to activate/deactivate things. + * A common usage is for "twistie" + * + * @memberof platform/commonUI/general + * @constructor + */ + function TimeConductorController($scope) { + var tickCount = 2; + + $scope.state = false; + + $scope.ticks = [0, 1]; + + function updateTicks() { + var i; + $scope.ticks = []; + for (i = 0; i < tickCount; i += 1) { + $scope.ticks.push(i / (tickCount - 1)); + } + } + + function updateSpanWidth(w) { + // Space about 100px apart + tickCount = Math.max(Math.floor(w / 100), 2); + updateTicks(); + } + + $scope.$watch("spanWidth", updateSpanWidth); + } + + return TimeConductorController; + } +); From a18cc50a43a3f35ec208ff09a83dfb7db1526ef4 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 2 Sep 2015 16:53:10 -0700 Subject: [PATCH 03/78] [Time Conductor] Begin binding control to data --- platform/commonUI/general/bundle.json | 2 +- .../templates/controls/time-controller.html | 4 +- .../controllers/TimeConductorController.js | 64 ++++++++++++++++--- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index cd239c7d08..73de6ba4a2 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -60,7 +60,7 @@ { "key": "TimeConductorController", "implementation": "controllers/TimeConductorController.js", - "depends": [ "$scope" ] + "depends": [ "$scope", "now" ] }, { "key": "TreeNodeController", diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 346b1b7143..97638bfbd7 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -25,7 +25,7 @@ properly on the range left and right bounds.
Start: - End: + End:
@@ -41,7 +41,7 @@ properly on the range left and right bounds.
{{startInnerText}}
-
{{startOuterText}}
+
{{endInnerText}}
diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js index a186651c0c..d87a844336 100644 --- a/platform/commonUI/general/src/controllers/TimeConductorController.js +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -22,29 +22,41 @@ /*global define,Promise*/ define( - [], - function () { + ['moment'], + function (moment) { "use strict"; + var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss"; + /** - * A ToggleController is used to activate/deactivate things. - * A common usage is for "twistie" - * * @memberof platform/commonUI/general * @constructor */ - function TimeConductorController($scope) { + function TimeConductorController($scope, now) { var tickCount = 2; $scope.state = false; + $scope.ticks = []; - $scope.ticks = [0, 1]; + function formatTimestamp(ts) { + return moment.utc(ts).format(DATE_FORMAT); + } + + // From 0.0-1.0 to "0%"-"1%" + function toPercent(p) { + return (100 * p) + "%"; + } function updateTicks() { - var i; + var i, p, ts, start, end, span; + end = $scope.ngModel.outer[1]; + start = $scope.ngModel.outer[0]; + span = end - start; $scope.ticks = []; for (i = 0; i < tickCount; i += 1) { - $scope.ticks.push(i / (tickCount - 1)); + p = i / (tickCount - 1); + ts = p * span + start; + $scope.ticks.push(formatTimestamp(ts)); } } @@ -54,7 +66,41 @@ define( updateTicks(); } + function updateFromParameters(ngModel) { + var t = now(), span; + + ngModel = ngModel || { + outer: [ t - 24 * 3600 * 1000, t ], + inner: [ t - 24 * 3600 * 1000, t ] + }; + + // First, dates for the date pickers for outer bounds + $scope.startOuterDate = new Date(ngModel.outer[0]); + $scope.endOuterDate = new Date(ngModel.outer[1]); + + // Then readable dates for the knobs + $scope.startInnerText = formatTimestamp(ngModel.inner[0]); + $scope.endInnerText = formatTimestamp(ngModel.inner[1]); + + // And positions for the knobs + span = ngModel.outer[1] - ngModel.outer[0]; + $scope.startInnerPct = + toPercent((ngModel.inner[0] - ngModel.outer[0]) / span); + $scope.endInnerPct = + toPercent((ngModel.outer[1] - ngModel.inner[1]) / span); + + // Stick it back is scope (in case we just set defaults) + $scope.ngModel = ngModel; + + updateTicks(); + } + + // Initialize scope to defaults + updateFromParameters(); + + $scope.$watchCollection("ngModel", updateFromParameters); $scope.$watch("spanWidth", updateSpanWidth); + } return TimeConductorController; From 57a947eaef6a0841231614decfa8ff6afe41361d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 2 Sep 2015 17:19:20 -0700 Subject: [PATCH 04/78] [Time Conductor] Accept drag gestures WTD-1515 --- .../templates/controls/time-controller.html | 12 ++- .../controllers/TimeConductorController.js | 75 ++++++++++++++++++- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 97638bfbd7..90e9510865 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -34,13 +34,21 @@ properly on the range left and right bounds. mct-resize="spanWidth = bounds.width">
-
+
{{startInnerText}}
-
+
{{endInnerText}}
diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js index d87a844336..17a4298577 100644 --- a/platform/commonUI/general/src/controllers/TimeConductorController.js +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -33,10 +33,10 @@ define( * @constructor */ function TimeConductorController($scope, now) { - var tickCount = 2; + var tickCount = 2, + initialDragValue; + - $scope.state = false; - $scope.ticks = []; function formatTimestamp(ts) { return moment.utc(ts).format(DATE_FORMAT); @@ -95,6 +95,75 @@ define( updateTicks(); } + function startLeftDrag() { + initialDragValue = $scope.ngModel.inner[0]; + } + + function startRightDrag() { + initialDragValue = $scope.ngModel.inner[1]; + } + + function startMiddleDrag() { + initialDragValue = [ + $scope.ngModel.inner[0], + $scope.ngModel.inner[0] + ]; + } + + function toMillis(pixels) { + var span = $scope.ngModel.outer[1] - $scope.ngModel.outer[0]; + return (pixels / $scope.spanWidth) * span; + } + + function clamp(value, low, high) { + return Math.max(low, Math.min(high, value)); + } + + function leftDrag(pixels) { + var delta = toMillis(pixels); + $scope.ngModel.inner[0] = clamp( + initialDragValue + delta, + $scope.ngModel.outer[0], + $scope.ngModel.inner[1] + ); + updateFromParameters($scope.ngModel); + } + + function rightDrag(pixels) { + var delta = toMillis(pixels); + $scope.ngModel.inner[1] = clamp( + initialDragValue + delta, + $scope.ngModel.inner[0], + $scope.ngModel.outer[1] + ); + updateFromParameters($scope.ngModel); + } + + function middleDrag(pixels) { + var delta = toMillis(pixels), + index = delta < 0 ? 0 : 1, + opposite = (index === 0) ? 1 : 0, + span = initialDragValue[opposite] - initialDragValue[index]; + + $scope.ngModel.inner[index] = clamp( + initialDragValue[index] + delta, + $scope.ngModel.outer[0], + $scope.ngModel.outer[1] + ); + $scope.ngModel.inner[opposite] = + $scope.ngModel.inner[index] + span; + } + + $scope.startLeftDrag = startLeftDrag; + $scope.startRightDrag = startRightDrag; + //$scope.startMiddleDrag = startMiddleDrag; + $scope.leftDrag = leftDrag; + $scope.rightDrag = rightDrag; + //$scope.middleDrag = middleDrag; + + $scope.state = false; + $scope.ticks = []; + // Initialize scope to defaults updateFromParameters(); From 9d6b70f43319e2d204be25b21ce58794f88ffff2 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 2 Sep 2015 17:25:41 -0700 Subject: [PATCH 05/78] [Time Conductor] Handle middle drag WTD-1515 --- .../src/controllers/TimeConductorController.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js index 17a4298577..ea978a10c3 100644 --- a/platform/commonUI/general/src/controllers/TimeConductorController.js +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -106,7 +106,7 @@ define( function startMiddleDrag() { initialDragValue = [ $scope.ngModel.inner[0], - $scope.ngModel.inner[0] + $scope.ngModel.inner[1] ]; } @@ -142,24 +142,27 @@ define( function middleDrag(pixels) { var delta = toMillis(pixels), index = delta < 0 ? 0 : 1, - opposite = (index === 0) ? 1 : 0, - span = initialDragValue[opposite] - initialDragValue[index]; + opposite = Math.abs(1 - index); + // Adjust the position of the edge in the direction of drag $scope.ngModel.inner[index] = clamp( initialDragValue[index] + delta, $scope.ngModel.outer[0], $scope.ngModel.outer[1] ); - $scope.ngModel.inner[opposite] = - $scope.ngModel.inner[index] + span; + // Adjust opposite knob to maintain span + $scope.ngModel.inner[opposite] = $scope.ngModel.inner[index] + + initialDragValue[opposite] - initialDragValue[index]; + + updateFromParameters($scope.ngModel); } $scope.startLeftDrag = startLeftDrag; $scope.startRightDrag = startRightDrag; - //$scope.startMiddleDrag = startMiddleDrag; + $scope.startMiddleDrag = startMiddleDrag; $scope.leftDrag = leftDrag; $scope.rightDrag = rightDrag; - //$scope.middleDrag = middleDrag; + $scope.middleDrag = middleDrag; $scope.state = false; $scope.ticks = []; From fc2860810b3f5e8a17bdd47c9731888f2d2f7e65 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 11:03:17 -0700 Subject: [PATCH 06/78] [Time Controller] Allow manual date entry WTD-1515 --- .../templates/controls/time-controller.html | 8 +++- .../controllers/TimeConductorController.js | 43 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 90e9510865..5f60741672 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -24,8 +24,12 @@ properly on the range left and right bounds.
- Start: - End: + Start: + End:
diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js index ea978a10c3..26f120c7b0 100644 --- a/platform/commonUI/general/src/controllers/TimeConductorController.js +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -36,12 +36,15 @@ define( var tickCount = 2, initialDragValue; - - function formatTimestamp(ts) { return moment.utc(ts).format(DATE_FORMAT); } + function parseTimestamp(text, fallback) { + var m = moment.utc(text, DATE_FORMAT); + return m.isValid() ? m.valueOf() : fallback; + } + // From 0.0-1.0 to "0%"-"1%" function toPercent(p) { return (100 * p) + "%"; @@ -66,17 +69,16 @@ define( updateTicks(); } - function updateFromParameters(ngModel) { + function updateViewFromModel(ngModel) { var t = now(), span; - ngModel = ngModel || { - outer: [ t - 24 * 3600 * 1000, t ], - inner: [ t - 24 * 3600 * 1000, t ] - }; + ngModel = ngModel || {}; + ngModel.outer = ngModel.outer || [ t - 24 * 3600 * 1000, t ]; + ngModel.inner = ngModel.inner || [ t - 24 * 3600 * 1000, t ]; // First, dates for the date pickers for outer bounds - $scope.startOuterDate = new Date(ngModel.outer[0]); - $scope.endOuterDate = new Date(ngModel.outer[1]); + $scope.startOuterDate = formatTimestamp(ngModel.outer[0]); + $scope.endOuterDate = formatTimestamp(ngModel.outer[1]); // Then readable dates for the knobs $scope.startInnerText = formatTimestamp(ngModel.inner[0]); @@ -126,7 +128,7 @@ define( $scope.ngModel.outer[0], $scope.ngModel.inner[1] ); - updateFromParameters($scope.ngModel); + updateViewFromModel($scope.ngModel); } function rightDrag(pixels) { @@ -136,7 +138,7 @@ define( $scope.ngModel.inner[0], $scope.ngModel.outer[1] ); - updateFromParameters($scope.ngModel); + updateViewFromModel($scope.ngModel); } function middleDrag(pixels) { @@ -154,7 +156,17 @@ define( $scope.ngModel.inner[opposite] = $scope.ngModel.inner[index] + initialDragValue[opposite] - initialDragValue[index]; - updateFromParameters($scope.ngModel); + updateViewFromModel($scope.ngModel); + } + + function updateOuterStart(text) { + var ngModel = $scope.ngModel; + ngModel.outer[0] = parseTimestamp(text, ngModel.outer[0]); + } + + function updateOuterEnd(text) { + var ngModel = $scope.ngModel; + ngModel.outer[1] = parseTimestamp(text, ngModel.outer[1]); } $scope.startLeftDrag = startLeftDrag; @@ -168,11 +180,12 @@ define( $scope.ticks = []; // Initialize scope to defaults - updateFromParameters(); + updateViewFromModel($scope.ngModel); - $scope.$watchCollection("ngModel", updateFromParameters); + $scope.$watchCollection("ngModel", updateViewFromModel); $scope.$watch("spanWidth", updateSpanWidth); - + $scope.$watch("startOuterDate", updateOuterStart); + $scope.$watch("endOuterDate", updateOuterEnd); } return TimeConductorController; From e4dec21ceb9f85daa798f5bd598f0b1c290a2fea Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 11:38:06 -0700 Subject: [PATCH 07/78] [Time Controller] Add telemetry capability wrapper WTD-1515 --- .../src/ConductorTelemetryCapability.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 platform/features/conductor/src/ConductorTelemetryCapability.js diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js new file mode 100644 index 0000000000..ea10bddb15 --- /dev/null +++ b/platform/features/conductor/src/ConductorTelemetryCapability.js @@ -0,0 +1,53 @@ +/***************************************************************************** + * 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'; + + function ConductorTelemetryCapability(timeConductor, telemetryCapability, domainObject) { + this.wrappedCapability = telemetryCapability; + } + + ConductorTelemetryCapability.prototype.getMetadata = function () { + return this.wrappedCapability.getMetadata; + }; + + ConductorTelemetryCapability.prototype.requestData = function (request) { + request = request || {}; + request.start = this.timeConductor.getStart(); + request.end = this.timeConductor.getEnd(); + return this.wrappedCapability.requestData(request); + }; + + ConductorTelemetryCapability.prototype.subscribe = function (callback, request) { + request = request || {}; + request.start = this.timeConductor.getStart(); + request.end = this.timeConductor.getEnd(); + return this.wrappedCapability.subscribe(callback, request); + }; + + return ConductorTelemetryCapability; + } +); From f74da6b93557649a14f246f796b3449dddc6039e Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 11:40:40 -0700 Subject: [PATCH 08/78] [Time Controller] Add overflow hidden Add overflow: hidden so that time controller does not exceed edges of the screen. WTD-1515 --- platform/features/conductor/src/ConductorRepresenter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 364cbe3296..5e68f2e2a4 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -28,7 +28,10 @@ define( var CONDUCTOR_HEIGHT = "100px", TEMPLATE = [ - '
', + '
', "", '
' ].join(''); From b668fb58fbef2e8f2b4b54c82b50b94ed540d2f1 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 11:44:11 -0700 Subject: [PATCH 09/78] [Time Controller] Show only outermost controller WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 5e68f2e2a4..5cbf1ce6e8 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -34,7 +34,8 @@ define( 'height: ' + CONDUCTOR_HEIGHT + '">', "", '
' - ].join(''); + ].join(''), + GLOBAL_SHOWING = false; /** * The ConductorRepresenter attaches the universal time conductor @@ -64,11 +65,9 @@ define( // Handle a specific representation of a specific domain object ConductorRepresenter.prototype.represent = function represent(representation, representedObject) { - if (this.showing) { - this.destroy(); - } + this.destroy(); - if (this.views.indexOf(representation) !== -1) { + if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) { // Create a new scope for the conductor this.conductorScope(this.getScope().$new()); this.conductorElement = @@ -77,11 +76,18 @@ define( this.element.addClass('abs'); this.element.css('bottom', CONDUCTOR_HEIGHT); this.showing = true; + GLOBAL_SHOWING = true; } }; // Respond to the destruction of the current representation. ConductorRepresenter.prototype.destroy = function destroy() { + // We may not have decided to show in the first place, + // so circumvent any unnecessary cleanup + if (!this.showing) { + return; + } + // Restore the original size of the mct-representation if (!this.hadAbs) { this.element.removeClass('abs'); @@ -101,6 +107,7 @@ define( } this.showing = false; + GLOBAL_SHOWING = false; }; return ConductorRepresenter; From 681cd0bb9cb561fdc0e244e8f94729731be6b968 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 14:53:23 -0700 Subject: [PATCH 10/78] [Time Controller] Add conductor service WTD-1515. --- .../conductor/src/ConductorService.js | 46 ++++++++++++++ .../src/ConductorTelemetryCapability.js | 10 +-- .../features/conductor/src/TimeConductor.js | 63 +++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 platform/features/conductor/src/ConductorService.js create mode 100644 platform/features/conductor/src/TimeConductor.js diff --git a/platform/features/conductor/src/ConductorService.js b/platform/features/conductor/src/ConductorService.js new file mode 100644 index 0000000000..c40838ff53 --- /dev/null +++ b/platform/features/conductor/src/ConductorService.js @@ -0,0 +1,46 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + ['./TimeConductor'], + function (TimeConductor) { + 'use strict'; + + var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24, + SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4; + + function ConductorService(now) { + var initialEnd = + Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS; + + this.conductor = + new TimeConductor(initialEnd - ONE_DAY_IN_MS, initialEnd); + } + + ConductorService.prototype.getConductor = function () { + return this.conductor; + }; + + return ConductorService; + } +); diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js index ea10bddb15..f1269a36e7 100644 --- a/platform/features/conductor/src/ConductorTelemetryCapability.js +++ b/platform/features/conductor/src/ConductorTelemetryCapability.js @@ -31,20 +31,20 @@ define( } ConductorTelemetryCapability.prototype.getMetadata = function () { - return this.wrappedCapability.getMetadata; + return this.wrappedCapability.getMetadata(); }; ConductorTelemetryCapability.prototype.requestData = function (request) { request = request || {}; - request.start = this.timeConductor.getStart(); - request.end = this.timeConductor.getEnd(); + request.start = this.timeConductor.queryStart(); + request.end = this.timeConductor.queryEnd(); return this.wrappedCapability.requestData(request); }; ConductorTelemetryCapability.prototype.subscribe = function (callback, request) { request = request || {}; - request.start = this.timeConductor.getStart(); - request.end = this.timeConductor.getEnd(); + request.start = this.timeConductor.queryStart(); + request.end = this.timeConductor.queryEnd(); return this.wrappedCapability.subscribe(callback, request); }; diff --git a/platform/features/conductor/src/TimeConductor.js b/platform/features/conductor/src/TimeConductor.js new file mode 100644 index 0000000000..d4eefeffd8 --- /dev/null +++ b/platform/features/conductor/src/TimeConductor.js @@ -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( + function () { + 'use strict'; + + function TimeConductor(start, end) { + this.inner = [ start, end ]; + this.outer = [ start, end ]; + } + + TimeConductor.prototype.queryStart = function (value) { + if (arguments.length > 0) { + this.outer[0] = value; + } + return this.outer[0]; + }; + + TimeConductor.prototype.queryEnd = function (value) { + if (arguments.length > 0) { + this.outer[1] = value; + } + return this.outer[1]; + }; + + TimeConductor.prototype.displayStart = function (value) { + if (arguments.length > 0) { + this.inner[0] = value; + } + return this.inner[0]; + }; + + TimeConductor.prototype.displayEnd = function (value) { + if (arguments.length > 0) { + this.inner[1] = value; + } + return this.inner[1]; + }; + + return TimeConductor; + } +); From dbfb8b9861f3e89502132bdcf90754d308d099a6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 14:58:49 -0700 Subject: [PATCH 11/78] [Time Controller] Add capability decorator WTD-1515 --- .../src/ConductorCapabilityDecorator.js | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 platform/features/conductor/src/ConductorCapabilityDecorator.js diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorCapabilityDecorator.js new file mode 100644 index 0000000000..b88d5cdbad --- /dev/null +++ b/platform/features/conductor/src/ConductorCapabilityDecorator.js @@ -0,0 +1,54 @@ +/***************************************************************************** + * 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( + ['./ConductorTelemetryCapability'], + function (ConductorTelemetryCapability) { + 'use strict'; + + function ConductorCapabilityDecorator(conductorService, capabilityService) { + this.conductorService = conductorService; + this.capabilityService = capabilityService; + } + + ConductorCapabilityDecorator.getCapabilities = function (model) { + var capabilities = this.capabilityService.getCapabilities(model), + TelemetryCapability = capabilities.telemetry, + conductorService = this.conductorService; + + if (TelemetryCapability) { + capabilities.telemetry = function (domainObject) { + return new ConductorCapabilityDecorator( + conductorService.getConductor(), + new TelemetryCapability(domainObject), + domainObject + ); + }; + } + + return capabilities; + }; + + return ConductorCapabilityDecorator; + } +); From bf4765fcb6f807562cfd3310725c5abb07c5dbda Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 15:13:03 -0700 Subject: [PATCH 12/78] [Time Controller] Bind displayed control to state Bind changes to the displayed time controller to changes to the underlying state of the time conductor, WTD-1515. --- platform/features/conductor/bundle.json | 15 +++++++++- .../conductor/src/ConductorRepresenter.js | 30 +++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/platform/features/conductor/bundle.json b/platform/features/conductor/bundle.json index d9905b6f24..139f9942c3 100644 --- a/platform/features/conductor/bundle.json +++ b/platform/features/conductor/bundle.json @@ -3,7 +3,20 @@ "representers": [ { "implementation": "ConductorRepresenter.js", - "depends": [ "$compile", "views[]" ] + "depends": [ "conductorService", "$compile", "views[]" ] + } + ], + "components": [ + { + "implementation": "ConductorCapabilityDecorator.js", + "depends": [ "conductorService" ] + } + ], + "services": [ + { + "key": "conductorService", + "implementation": "ConductorService.js", + "depends": [ "now" ] } ] } diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 5cbf1ce6e8..e7fc76aec5 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -32,7 +32,8 @@ define( '"position: absolute; bottom: 0; width: 100%; ', 'overflow: hidden; ', 'height: ' + CONDUCTOR_HEIGHT + '">', - "", + "", + "", '
' ].join(''), GLOBAL_SHOWING = false; @@ -45,7 +46,7 @@ define( * @implements {Representer} * @constructor */ - function ConductorRepresenter($compile, views, scope, element) { + function ConductorRepresenter(conductorService, $compile, views, scope, element) { var conductorScope; // Angular doesn't like objects to retain references to scope @@ -54,6 +55,7 @@ define( return (conductorScope = arguments.length > 0 ? s : conductorScope); }; + this.conductorService = conductorService; this.element = element; this.showing = false; this.views = views; @@ -63,6 +65,29 @@ define( this.hadAbs = element.hasClass('abs'); } + // Update the time conductor from the scope + ConductorRepresenter.prototype.wireScope = function () { + var scope = this.conductorScope(), + conductor = this.conductorService.getConductor(); + + function updateConductor() { + conductor.queryStart(scope.conductor.outer[0]); + conductor.queryEnd(scope.conductor.outer[1]); + conductor.displayStart(scope.conductor.inner[0]); + conductor.displayEnd(scope.conductor.inner[1]); + } + + scope.conductor = { + outer: [ conductor.queryStart(), conductor.queryEnd() ], + inner: [ conductor.displayStart(), conductor.displayEnd() ] + }; + + scope.$watch('conductor.outer[0]', updateConductor); + scope.$watch('conductor.outer[1]', updateConductor); + scope.$watch('conductor.inner[0]', updateConductor); + scope.$watch('conductor.inner[1]', updateConductor); + }; + // Handle a specific representation of a specific domain object ConductorRepresenter.prototype.represent = function represent(representation, representedObject) { this.destroy(); @@ -70,6 +95,7 @@ define( if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) { // Create a new scope for the conductor this.conductorScope(this.getScope().$new()); + this.wireScope(); this.conductorElement = this.$compile(TEMPLATE)(this.conductorScope()); this.element.after(this.conductorElement[0]); From 436e010738e5f5a2336b5f262362f9b79186973d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 3 Sep 2015 15:59:46 -0700 Subject: [PATCH 13/78] [Time Conductor] Broadcast changes WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index e7fc76aec5..7dec43da72 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -46,7 +46,7 @@ define( * @implements {Representer} * @constructor */ - function ConductorRepresenter(conductorService, $compile, views, scope, element) { + function ConductorRepresenter($interval, conductorService, $compile, views, scope, element) { var conductorScope; // Angular doesn't like objects to retain references to scope @@ -60,6 +60,7 @@ define( this.showing = false; this.views = views; this.$compile = $compile; + this.$interval = $interval; this.originalHeight = element.css('height'); this.hadAbs = element.hasClass('abs'); @@ -68,13 +69,25 @@ define( // Update the time conductor from the scope ConductorRepresenter.prototype.wireScope = function () { var scope = this.conductorScope(), - conductor = this.conductorService.getConductor(); + conductor = this.conductorService.getConductor(), + self = this; - function updateConductor() { + function updateConductorOuter() { conductor.queryStart(scope.conductor.outer[0]); conductor.queryEnd(scope.conductor.outer[1]); + self.getScope().$broadcast('telemetry:query:bounds', { + start: conductor.queryStart(), + end: conductor.queryEnd() + }); + } + + function updateConductorInner() { conductor.displayStart(scope.conductor.inner[0]); conductor.displayEnd(scope.conductor.inner[1]); + self.getScope().$broadcast('telemetry:display:bounds', { + start: conductor.displayStart(), + end: conductor.displayEnd() + }); } scope.conductor = { @@ -82,10 +95,10 @@ define( inner: [ conductor.displayStart(), conductor.displayEnd() ] }; - scope.$watch('conductor.outer[0]', updateConductor); - scope.$watch('conductor.outer[1]', updateConductor); - scope.$watch('conductor.inner[0]', updateConductor); - scope.$watch('conductor.inner[1]', updateConductor); + scope.$watch('conductor.outer[0]', updateConductorOuter); + scope.$watch('conductor.outer[1]', updateConductorOuter); + scope.$watch('conductor.inner[0]', updateConductorInner); + scope.$watch('conductor.inner[1]', updateConductorInner); }; // Handle a specific representation of a specific domain object From 35ff4efbca4db96ac5c60c66f885aabe8dc8202f Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 09:44:08 -0700 Subject: [PATCH 14/78] [Time Conductor] Add placeholder specs Add empty specs for classes related to time conductor, WTD-1515 --- .../test/ConductorCapabilityDecoratorSpec.js | 36 +++++++++++++++++++ .../test/ConductorRepresenterSpec.js | 36 +++++++++++++++++++ .../conductor/test/ConductorServiceSpec.js | 36 +++++++++++++++++++ .../test/ConductorTelemetryCapabilitySpec.js | 36 +++++++++++++++++++ .../conductor/test/TimeConductorSpec.js | 36 +++++++++++++++++++ platform/features/conductor/test/suite.json | 7 ++++ 6 files changed, 187 insertions(+) create mode 100644 platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js create mode 100644 platform/features/conductor/test/ConductorRepresenterSpec.js create mode 100644 platform/features/conductor/test/ConductorServiceSpec.js create mode 100644 platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js create mode 100644 platform/features/conductor/test/TimeConductorSpec.js create mode 100644 platform/features/conductor/test/suite.json diff --git a/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js b/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js new file mode 100644 index 0000000000..d0f4dda4fc --- /dev/null +++ b/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. + */ +define( + ["../src/ConductorCapabilityDecorator"], + function (ConductorCapabilityDecorator) { + "use strict"; + + describe("ConductorCapabilityDecorator", function () { + + }); + } +); diff --git a/platform/features/conductor/test/ConductorRepresenterSpec.js b/platform/features/conductor/test/ConductorRepresenterSpec.js new file mode 100644 index 0000000000..ab5012930a --- /dev/null +++ b/platform/features/conductor/test/ConductorRepresenterSpec.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. + */ +define( + ["../src/ConductorRepresenter"], + function (ConductorRepresenter) { + "use strict"; + + describe("ConductorRepresenter", function () { + + }); + } +); diff --git a/platform/features/conductor/test/ConductorServiceSpec.js b/platform/features/conductor/test/ConductorServiceSpec.js new file mode 100644 index 0000000000..152faa462a --- /dev/null +++ b/platform/features/conductor/test/ConductorServiceSpec.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. + */ +define( + ["../src/ConductorService"], + function (ConductorService) { + "use strict"; + + describe("ConductorService", function () { + + }); + } +); diff --git a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js new file mode 100644 index 0000000000..cdcee1b39a --- /dev/null +++ b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. + */ +define( + ["../src/ConductorTelemetryCapability"], + function (ConductorTelemetryCapability) { + "use strict"; + + describe("ConductorTelemetryCapability", function () { + + }); + } +); diff --git a/platform/features/conductor/test/TimeConductorSpec.js b/platform/features/conductor/test/TimeConductorSpec.js new file mode 100644 index 0000000000..8e95f6f798 --- /dev/null +++ b/platform/features/conductor/test/TimeConductorSpec.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. + */ +define( + ["../src/TimeConductor"], + function (TimeConductor) { + "use strict"; + + describe("TimeConductor", function () { + + }); + } +); diff --git a/platform/features/conductor/test/suite.json b/platform/features/conductor/test/suite.json new file mode 100644 index 0000000000..85a155c5df --- /dev/null +++ b/platform/features/conductor/test/suite.json @@ -0,0 +1,7 @@ +[ + "ConductorCapabilityDecorator", + "ConductorRepresenter", + "ConductorService", + "ConductorTelemetryCapability", + "TimeConductor" +] From a481b377cba67addc838dbb3f24b2b73d865123d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 10:24:14 -0700 Subject: [PATCH 15/78] [Time Conductor] Add terminology note to readme WTD-1515 --- platform/features/conductor/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform/features/conductor/README.md b/platform/features/conductor/README.md index 97c7dde3c2..196093ae1f 100644 --- a/platform/features/conductor/README.md +++ b/platform/features/conductor/README.md @@ -1,3 +1,9 @@ Provides the time conductor, a control which appears at the bottom of the screen allowing telemetry start and end times to be modified. + +Note that the term "time controller" is generally preferred +outside of the code base (e.g. in UI documents, issues, etc.); +the term "time conductor" is being used in code to avoid +confusion with "controllers" in the Model-View-Controller +sense. From f83588d980a1aaf30d342e18fb6fd0a61b9c3e2a Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 10:32:01 -0700 Subject: [PATCH 16/78] [Time Controller] Begin adding test cases WTD-1515 --- .../conductor/test/ConductorServiceSpec.js | 24 ++++++++++++++++- .../conductor/test/TimeConductorSpec.js | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/test/ConductorServiceSpec.js b/platform/features/conductor/test/ConductorServiceSpec.js index 152faa462a..5146ca5f42 100644 --- a/platform/features/conductor/test/ConductorServiceSpec.js +++ b/platform/features/conductor/test/ConductorServiceSpec.js @@ -29,8 +29,30 @@ define( function (ConductorService) { "use strict"; - describe("ConductorService", function () { + var TEST_NOW = 1020304050; + describe("ConductorService", function () { + var mockNow, + conductorService; + + beforeEach(function () { + mockNow = jasmine.createSpy('now'); + mockNow.andReturn(TEST_NOW); + conductorService = new ConductorService(mockNow); + }); + + it("initializes a time conductor around the current time", function () { + var conductor = conductorService.getConductor(); + expect(conductor.queryStart() <= TEST_NOW).toBeTruthy(); + expect(conductor.queryEnd() >= TEST_NOW).toBeTruthy(); + expect(conductor.queryEnd() > conductor.queryStart()) + .toBeTruthy(); + }); + + it("provides a single shared time conductor instance", function () { + expect(conductorService.getConductor()) + .toBe(conductorService.getConductor()); + }); }); } ); diff --git a/platform/features/conductor/test/TimeConductorSpec.js b/platform/features/conductor/test/TimeConductorSpec.js index 8e95f6f798..558322329e 100644 --- a/platform/features/conductor/test/TimeConductorSpec.js +++ b/platform/features/conductor/test/TimeConductorSpec.js @@ -30,6 +30,33 @@ define( "use strict"; describe("TimeConductor", function () { + var testStart, + testEnd, + conductor; + + beforeEach(function () { + testStart = 42; + testEnd = 12321; + conductor = new TimeConductor(testStart, testEnd); + }); + + it("provides accessors for query/display start/end times", function () { + expect(conductor.queryStart()).toEqual(testStart); + expect(conductor.queryEnd()).toEqual(testEnd); + expect(conductor.displayStart()).toEqual(testStart); + expect(conductor.displayEnd()).toEqual(testEnd); + }); + + it("provides setters for query/display start/end times", function () { + expect(conductor.queryStart(1)).toEqual(1); + expect(conductor.queryEnd(2)).toEqual(2); + expect(conductor.displayStart(3)).toEqual(3); + expect(conductor.displayEnd(4)).toEqual(4); + expect(conductor.queryStart()).toEqual(1); + expect(conductor.queryEnd()).toEqual(2); + expect(conductor.displayStart()).toEqual(3); + expect(conductor.displayEnd()).toEqual(4); + }); }); } From 9ccd0b9188c341f9727cdf355a79ef9a9ba2cae5 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 10:47:38 -0700 Subject: [PATCH 17/78] [Time Conductor] Test capability decorator WTD-1515 --- .../src/ConductorCapabilityDecorator.js | 2 +- .../test/ConductorCapabilityDecoratorSpec.js | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorCapabilityDecorator.js index b88d5cdbad..2ad5982e34 100644 --- a/platform/features/conductor/src/ConductorCapabilityDecorator.js +++ b/platform/features/conductor/src/ConductorCapabilityDecorator.js @@ -31,7 +31,7 @@ define( this.capabilityService = capabilityService; } - ConductorCapabilityDecorator.getCapabilities = function (model) { + ConductorCapabilityDecorator.prototype.getCapabilities = function (model) { var capabilities = this.capabilityService.getCapabilities(model), TelemetryCapability = capabilities.telemetry, conductorService = this.conductorService; diff --git a/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js b/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js index d0f4dda4fc..b98270148b 100644 --- a/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js +++ b/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js @@ -30,7 +30,72 @@ define( "use strict"; describe("ConductorCapabilityDecorator", function () { + var mockCapabilityService, + mockConductorService, + testModel, + testCapabilities, + decorator; + function instantiate(Constructor) { + return new Constructor(); + } + + beforeEach(function () { + testCapabilities = { + telemetry: jasmine.createSpy('Telemetry'), + other: jasmine.createSpy('Other') + }; + + mockCapabilityService = jasmine.createSpyObj( + 'capabilityService', + [ 'getCapabilities' ] + ); + mockConductorService = jasmine.createSpyObj( + 'conductorService', + [ 'getConductor' ] + ); + testModel = { someKey: "some value" }; + + mockCapabilityService.getCapabilities.andCallFake(function () { + // Wrap with object.create so we can still + // reliably expect properties of testCapabilities itself + return Object.create(testCapabilities); + }); + + decorator = new ConductorCapabilityDecorator( + mockConductorService, + mockCapabilityService + ); + }); + + it("delegates to the decorated capability service", function () { + expect(mockCapabilityService.getCapabilities).not.toHaveBeenCalled(); + decorator.getCapabilities(testModel); + expect(mockCapabilityService.getCapabilities).toHaveBeenCalled(); + }); + + it("wraps the 'telemetry' capability of objects", function () { + var capabilities = decorator.getCapabilities(testModel); + expect(capabilities.telemetry) + .not.toBe(testCapabilities.telemetry); + + // Should wrap - verify by invocation + expect(testCapabilities.telemetry).not.toHaveBeenCalled(); + instantiate(capabilities.telemetry); + expect(testCapabilities.telemetry).toHaveBeenCalled(); + }); + + it("does not wrap other capabilities", function () { + var capabilities = decorator.getCapabilities(testModel); + expect(capabilities.other) + .toBe(testCapabilities.other); + }); + + it("gets a time conductor from the conductorService", function () { + expect(mockConductorService.getConductor).not.toHaveBeenCalled(); + instantiate(decorator.getCapabilities(testModel).telemetry); + expect(mockConductorService.getConductor).toHaveBeenCalled(); + }); }); } ); From 8a76c3a42540b01482a2265228bd453041c668c5 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 10:57:50 -0700 Subject: [PATCH 18/78] [Time Controller] Test conductor's telemetry capability WTD-1515 --- .../src/ConductorCapabilityDecorator.js | 3 +- .../src/ConductorTelemetryCapability.js | 6 +- .../test/ConductorTelemetryCapabilitySpec.js | 65 +++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorCapabilityDecorator.js index 2ad5982e34..ad4115d48c 100644 --- a/platform/features/conductor/src/ConductorCapabilityDecorator.js +++ b/platform/features/conductor/src/ConductorCapabilityDecorator.js @@ -40,8 +40,7 @@ define( capabilities.telemetry = function (domainObject) { return new ConductorCapabilityDecorator( conductorService.getConductor(), - new TelemetryCapability(domainObject), - domainObject + new TelemetryCapability(domainObject) ); }; } diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js index f1269a36e7..02950ac8f2 100644 --- a/platform/features/conductor/src/ConductorTelemetryCapability.js +++ b/platform/features/conductor/src/ConductorTelemetryCapability.js @@ -26,7 +26,8 @@ define( function () { 'use strict'; - function ConductorTelemetryCapability(timeConductor, telemetryCapability, domainObject) { + function ConductorTelemetryCapability(timeConductor, telemetryCapability) { + this.timeConductor = timeConductor; this.wrappedCapability = telemetryCapability; } @@ -42,9 +43,6 @@ define( }; ConductorTelemetryCapability.prototype.subscribe = function (callback, request) { - request = request || {}; - request.start = this.timeConductor.queryStart(); - request.end = this.timeConductor.queryEnd(); return this.wrappedCapability.subscribe(callback, request); }; diff --git a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js index cdcee1b39a..e5c9066e22 100644 --- a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js +++ b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js @@ -30,6 +30,71 @@ define( "use strict"; describe("ConductorTelemetryCapability", function () { + var mockConductor, + mockTelemetryCapability, + mockUnsubscribe, + testMetadata, + testStartTime, + testEndTime, + conductorTelemetryCapability; + + beforeEach(function () { + mockConductor = jasmine.createSpyObj( + 'timeConductor', + [ + 'queryStart', + 'queryEnd', + 'displayStart', + 'displayEnd' + ] + ); + mockTelemetryCapability = jasmine.createSpyObj( + 'telemetry', + [ 'getMetadata', 'requestData', 'subscribe' ] + ); + mockUnsubscribe = jasmine.createSpy('unsubscribe'); + + testStartTime = 42; + testEndTime = 12321; + testMetadata = { someKey: 'some value' }; + mockTelemetryCapability.getMetadata.andReturn(testMetadata); + mockTelemetryCapability.subscribe.andReturn(mockUnsubscribe); + mockConductor.queryStart.andReturn(testStartTime); + mockConductor.queryEnd.andReturn(testEndTime); + + conductorTelemetryCapability = new ConductorTelemetryCapability( + mockConductor, + mockTelemetryCapability + ); + }); + + it("simply delegates getMetadata calls", function () { + expect(conductorTelemetryCapability.getMetadata()) + .toBe(testMetadata); + }); + + it("adds start/end times to requests", function () { + conductorTelemetryCapability + .requestData({ someKey: "some value" }); + expect(mockTelemetryCapability.requestData).toHaveBeenCalledWith({ + someKey: "some value", + start: testStartTime, + end: testEndTime + }); + }); + + it("simply delegates subscribe calls", function () { + var mockCallback = jasmine.createSpy('callback'), + testRequest = { someKey: "some value" }; + expect(conductorTelemetryCapability.subscribe( + mockCallback, + testRequest + )).toBe(mockUnsubscribe); + expect(mockTelemetryCapability.subscribe).toHaveBeenCalledWith( + mockCallback, + { someKey: "some value" } + ); + }); }); } From 5c1d209effe6efccdfd88e1b2993227672be26b4 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 11:53:51 -0700 Subject: [PATCH 19/78] [Time Controller] Simplify ConductorRepresenter WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 7dec43da72..d75edefd13 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -46,75 +46,75 @@ define( * @implements {Representer} * @constructor */ - function ConductorRepresenter($interval, conductorService, $compile, views, scope, element) { + function ConductorRepresenter(conductorService, $compile, views, scope, element) { var conductorScope; - // Angular doesn't like objects to retain references to scope - this.getScope = function () { return scope; }; + // Angular doesn't like objects to retain references to scopes + this.getScope = function () { + return scope; + }; this.conductorScope = function (s) { return (conductorScope = arguments.length > 0 ? s : conductorScope); }; this.conductorService = conductorService; this.element = element; - this.showing = false; this.views = views; this.$compile = $compile; - this.$interval = $interval; - - this.originalHeight = element.css('height'); - this.hadAbs = element.hasClass('abs'); } // Update the time conductor from the scope - ConductorRepresenter.prototype.wireScope = function () { - var scope = this.conductorScope(), - conductor = this.conductorService.getConductor(), - self = this; - + function wireScope(conductor, conductorScope, repScope) { function updateConductorOuter() { - conductor.queryStart(scope.conductor.outer[0]); - conductor.queryEnd(scope.conductor.outer[1]); - self.getScope().$broadcast('telemetry:query:bounds', { + conductor.queryStart(conductorScope.conductor.outer[0]); + conductor.queryEnd(conductorScope.conductor.outer[1]); + repScope.$broadcast('telemetry:query:bounds', { start: conductor.queryStart(), end: conductor.queryEnd() }); } function updateConductorInner() { - conductor.displayStart(scope.conductor.inner[0]); - conductor.displayEnd(scope.conductor.inner[1]); - self.getScope().$broadcast('telemetry:display:bounds', { + conductor.displayStart(conductorScope.conductor.inner[0]); + conductor.displayEnd(conductorScope.conductor.inner[1]); + repScope.$broadcast('telemetry:display:bounds', { start: conductor.displayStart(), end: conductor.displayEnd() }); } - scope.conductor = { + conductorScope.conductor = { outer: [ conductor.queryStart(), conductor.queryEnd() ], inner: [ conductor.displayStart(), conductor.displayEnd() ] }; - scope.$watch('conductor.outer[0]', updateConductorOuter); - scope.$watch('conductor.outer[1]', updateConductorOuter); - scope.$watch('conductor.inner[0]', updateConductorInner); - scope.$watch('conductor.inner[1]', updateConductorInner); - }; + conductorScope.$watch('conductor.outer[0]', updateConductorOuter); + conductorScope.$watch('conductor.outer[1]', updateConductorOuter); + conductorScope.$watch('conductor.inner[0]', updateConductorInner); + conductorScope.$watch('conductor.inner[1]', updateConductorInner); + } // Handle a specific representation of a specific domain object ConductorRepresenter.prototype.represent = function represent(representation, representedObject) { this.destroy(); if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) { + // Track original states + this.originalHeight = this.element.css('height'); + this.hadAbs = this.element.hasClass('abs'); + // Create a new scope for the conductor this.conductorScope(this.getScope().$new()); - this.wireScope(); + this.wireScope( + this.conductorService.getConductor(), + this.conductorScope(), + this.getScope() + ); this.conductorElement = this.$compile(TEMPLATE)(this.conductorScope()); this.element.after(this.conductorElement[0]); this.element.addClass('abs'); this.element.css('bottom', CONDUCTOR_HEIGHT); - this.showing = true; GLOBAL_SHOWING = true; } }; @@ -123,7 +123,7 @@ define( ConductorRepresenter.prototype.destroy = function destroy() { // We may not have decided to show in the first place, // so circumvent any unnecessary cleanup - if (!this.showing) { + if (!this.conductorElement) { return; } @@ -145,7 +145,6 @@ define( this.conductorScope(undefined); } - this.showing = false; GLOBAL_SHOWING = false; }; From af462ff3ee114662272611853c679c20682c4892 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 12:12:21 -0700 Subject: [PATCH 20/78] [Time Controller] Begin adding mocks Begin adding/configuring mocks to support testing ConductorRepresenter, WTD-1515 --- .../test/ConductorRepresenterSpec.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/platform/features/conductor/test/ConductorRepresenterSpec.js b/platform/features/conductor/test/ConductorRepresenterSpec.js index ab5012930a..d096131a89 100644 --- a/platform/features/conductor/test/ConductorRepresenterSpec.js +++ b/platform/features/conductor/test/ConductorRepresenterSpec.js @@ -29,7 +29,48 @@ define( function (ConductorRepresenter) { "use strict"; + var SCOPE_METHODS = [ + '$on', + '$watch', + '$broadcast', + '$emit', + '$new', + '$destroy' + ], + ELEMENT_METHODS = [ + 'hasClass', + 'addClass', + 'css' + ]; + describe("ConductorRepresenter", function () { + var mockConductorService, + mockCompile, + testViews, + mockScope, + mockElement, + mockConductor, + mockCompiledTemplate, + mockNewScope, + mockNewElement, + representer; + + beforeEach(function () { + mockConductorService = jasmine.createSpyObj( + 'conductorService', + ['getConductor'] + ); + mockCompile = jasmine.createSpy('$compile'); + testViews = [ { someKey: "some value" } ]; + mockScope = jasmine.createSpyObj('scope', SCOPE_METHODS); + mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS); + mockConductor = jasmine.createSpyObj( + 'conductor', + [ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ] + ); + mockCompiledTemplate = jasmine.createSpy('template'); + mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS); + }); }); } From 01a6d2e6a71505b4e33bb5ed4255b16c846880a1 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 12:44:49 -0700 Subject: [PATCH 21/78] [Time Controller] Test ConductorRepresenter WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 2 +- .../test/ConductorRepresenterSpec.js | 94 ++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index d75edefd13..2583c50b14 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -105,7 +105,7 @@ define( // Create a new scope for the conductor this.conductorScope(this.getScope().$new()); - this.wireScope( + wireScope( this.conductorService.getConductor(), this.conductorScope(), this.getScope() diff --git a/platform/features/conductor/test/ConductorRepresenterSpec.js b/platform/features/conductor/test/ConductorRepresenterSpec.js index d096131a89..931eec506f 100644 --- a/platform/features/conductor/test/ConductorRepresenterSpec.js +++ b/platform/features/conductor/test/ConductorRepresenterSpec.js @@ -19,7 +19,7 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ +/*global define,describe,it,expect,beforeEach,waitsFor,afterEach,jasmine*/ /** * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. @@ -40,7 +40,10 @@ define( ELEMENT_METHODS = [ 'hasClass', 'addClass', - 'css' + 'removeClass', + 'css', + 'after', + 'remove' ]; describe("ConductorRepresenter", function () { @@ -55,6 +58,14 @@ define( mockNewElement, representer; + function fireWatch(scope, watch, value) { + scope.$watch.calls.forEach(function (call) { + if (call.args[0] === watch) { + call.args[1](value); + } + }); + } + beforeEach(function () { mockConductorService = jasmine.createSpyObj( 'conductorService', @@ -70,6 +81,85 @@ define( ); mockCompiledTemplate = jasmine.createSpy('template'); mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS); + mockNewElement = jasmine.createSpyObj('newElement', ELEMENT_METHODS); + mockNewElement[0] = mockNewElement; + + mockConductorService.getConductor.andReturn(mockConductor); + mockCompile.andReturn(mockCompiledTemplate); + mockCompiledTemplate.andReturn(mockNewElement); + mockScope.$new.andReturn(mockNewScope); + + representer = new ConductorRepresenter( + mockConductorService, + mockCompile, + testViews, + mockScope, + mockElement + ); + }); + + afterEach(function () { + representer.destroy(); + }); + + it("adds a conductor to views", function () { + representer.represent(testViews[0], {}); + expect(mockElement.after).toHaveBeenCalledWith(mockNewElement); + }); + + it("adds nothing to non-view representations", function () { + representer.represent({ someKey: "something else" }, {}); + expect(mockElement.after).not.toHaveBeenCalled(); + }); + + it("removes the conductor when destroyed", function () { + representer.represent(testViews[0], {}); + expect(mockNewElement.remove).not.toHaveBeenCalled(); + representer.destroy(); + expect(mockNewElement.remove).toHaveBeenCalled(); + }); + + it("destroys any new scope created", function () { + representer.represent(testViews[0], {}); + representer.destroy(); + expect(mockNewScope.$destroy.calls.length) + .toEqual(mockScope.$new.calls.length); + }); + + it("exposes conductor state in scope", function () { + mockConductor.queryStart.andReturn(42); + mockConductor.queryEnd.andReturn(12321); + mockConductor.displayStart.andReturn(1977); + mockConductor.displayEnd.andReturn(1984); + representer.represent(testViews[0], {}); + + expect(mockNewScope.conductor).toEqual({ + inner: [ 1977, 1984 ], + outer: [ 42, 12321 ] + }); + }); + + it("updates conductor state from scope", function () { + var testState = { + inner: [ 42, 1984 ], + outer: [ -1977, 12321 ] + }; + + representer.represent(testViews[0], {}); + + mockNewScope.conductor = testState; + + fireWatch(mockNewScope, 'conductor.inner[0]', testState.inner[0]); + expect(mockConductor.displayStart).toHaveBeenCalledWith(42); + + fireWatch(mockNewScope, 'conductor.inner[1]', testState.inner[1]); + expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984); + + fireWatch(mockNewScope, 'conductor.outer[0]', -1977); + expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977); + + fireWatch(mockNewScope, 'conductor.outer[1]', 12321); + expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321); }); }); From bfb19dea744c5f1593a172b5cc189e0710bf570d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 12:52:02 -0700 Subject: [PATCH 22/78] [Time Controller] Use start time in example WTD-1515 --- example/generator/src/SinewaveTelemetryProvider.js | 8 ++++---- ...ewaveTelemetry.js => SinewaveTelemetrySeries.js} | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) rename example/generator/src/{SinewaveTelemetry.js => SinewaveTelemetrySeries.js} (84%) diff --git a/example/generator/src/SinewaveTelemetryProvider.js b/example/generator/src/SinewaveTelemetryProvider.js index 014510f67c..c4062e659c 100644 --- a/example/generator/src/SinewaveTelemetryProvider.js +++ b/example/generator/src/SinewaveTelemetryProvider.js @@ -25,8 +25,8 @@ * Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14. */ define( - ["./SinewaveTelemetry"], - function (SinewaveTelemetry) { + ["./SinewaveTelemetrySeries"], + function (SinewaveTelemetrySeries) { "use strict"; /** @@ -45,7 +45,7 @@ define( function generateData(request) { return { key: request.key, - telemetry: new SinewaveTelemetry(request) + telemetry: new SinewaveTelemetrySeries(request) }; } @@ -112,4 +112,4 @@ define( return SinewaveTelemetryProvider; } -); \ No newline at end of file +); diff --git a/example/generator/src/SinewaveTelemetry.js b/example/generator/src/SinewaveTelemetrySeries.js similarity index 84% rename from example/generator/src/SinewaveTelemetry.js rename to example/generator/src/SinewaveTelemetrySeries.js index 6c255bf56a..9ed2320a4a 100644 --- a/example/generator/src/SinewaveTelemetry.js +++ b/example/generator/src/SinewaveTelemetrySeries.js @@ -35,9 +35,10 @@ define( * * @constructor */ - function SinewaveTelemetry(request) { - var latestObservedTime = Date.now(), - count = Math.floor((latestObservedTime - firstObservedTime) / 1000), + function SinewaveTelemetrySeries(request) { + var firstTime = (request || {}).start || firstObservedTime, + latestObservedTime = Date.now(), + count = Math.floor((latestObservedTime - firstTime) / 1000), period = request.period || 30, generatorData = {}; @@ -47,7 +48,7 @@ define( generatorData.getDomainValue = function (i, domain) { return i * 1000 + - (domain !== 'delta' ? firstObservedTime : 0); + (domain !== 'delta' ? firstTime : 0); }; generatorData.getRangeValue = function (i, range) { @@ -58,6 +59,6 @@ define( return generatorData; } - return SinewaveTelemetry; + return SinewaveTelemetrySeries; } -); \ No newline at end of file +); From 3ce40ab870bd9a43dfac79fb0055d2b9e402a4f6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 13:02:36 -0700 Subject: [PATCH 23/78] [Time Controller] Fix capability decoration WTD-1515 --- platform/features/conductor/bundle.json | 2 ++ platform/features/conductor/src/ConductorCapabilityDecorator.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/bundle.json b/platform/features/conductor/bundle.json index 139f9942c3..39d45628bb 100644 --- a/platform/features/conductor/bundle.json +++ b/platform/features/conductor/bundle.json @@ -8,6 +8,8 @@ ], "components": [ { + "type": "decorator", + "provides": "capabilityService", "implementation": "ConductorCapabilityDecorator.js", "depends": [ "conductorService" ] } diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorCapabilityDecorator.js index ad4115d48c..85b34152f3 100644 --- a/platform/features/conductor/src/ConductorCapabilityDecorator.js +++ b/platform/features/conductor/src/ConductorCapabilityDecorator.js @@ -38,7 +38,7 @@ define( if (TelemetryCapability) { capabilities.telemetry = function (domainObject) { - return new ConductorCapabilityDecorator( + return new ConductorTelemetryCapability( conductorService.getConductor(), new TelemetryCapability(domainObject) ); From c2985d61b7723b0fab74d4343d90aa05018afdbf Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 13:57:26 -0700 Subject: [PATCH 24/78] [Plot] Follow universal time controller Follow displayable area of universal time controller, WTD-1515 --- platform/features/plot/src/PlotController.js | 15 ++++++- .../src/elements/PlotPanZoomStackGroup.js | 3 +- .../features/plot/src/elements/PlotUpdater.js | 41 ++++++++++++++++--- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index a54fff83dd..be126e2ed2 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -138,6 +138,16 @@ define( } } + // Change the displayable bounds + function setBasePanZoom(event, bounds) { + var start = bounds.start, + end = bounds.end; + if (updater) { + updater.setDomainBounds(start, end); + self.update(); + } + } + // Create a new subscription; telemetrySubscriber gets // to do the meaningful work here. function subscribe(domainObject) { @@ -172,11 +182,14 @@ define( this.scheduleUpdate = throttle(function () { self.modeOptions.getModeHandler().getSubPlots() .forEach(updateSubplot); - }); + }, 50); // Subscribe to telemetry when a domain object becomes available $scope.$watch('domainObject', subscribe); + // Respond to external bounds changes + $scope.$on("telemetry:display:bounds", setBasePanZoom); + // Unsubscribe when the plot is destroyed $scope.$on("$destroy", releaseSubscription); diff --git a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js b/platform/features/plot/src/elements/PlotPanZoomStackGroup.js index 3746958ecb..e1b61a06eb 100644 --- a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js +++ b/platform/features/plot/src/elements/PlotPanZoomStackGroup.js @@ -143,8 +143,7 @@ define( PlotPanZoomStackGroup.prototype.getDepth = function () { // All stacks are kept in sync, so look up depth // from the first one. - return this.stacks.length > 0 ? - this.stacks[0].getDepth() : 0; + return this.stacks.length > 0 ? this.stacks[0].getDepth() : 0; }; /** diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js index 851fa56096..c30786986d 100644 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ b/platform/features/plot/src/elements/PlotUpdater.js @@ -141,10 +141,10 @@ define( PlotUpdater.prototype.initializeDomainOffset = function (values) { this.domainOffset = ((this.domainOffset === undefined) && (values.length > 0)) ? - (values.reduce(function (a, b) { - return (a || 0) + (b || 0); - }, 0) / values.length) : - this.domainOffset; + (values.reduce(function (a, b) { + return (a || 0) + (b || 0); + }, 0) / values.length) : + this.domainOffset; }; // Expand range slightly so points near edges are visible @@ -159,7 +159,10 @@ define( // Update dimensions and origin based on extrema of plots PlotUpdater.prototype.updateBounds = function () { - var bufferArray = this.bufferArray; + var bufferArray = this.bufferArray, + priorDomainOrigin = this.origin[0], + priorDomainDimensions = this.dimensions[0]; + if (bufferArray.length > 0) { this.domainExtrema = bufferArray.map(function (lineBuffer) { return lineBuffer.getDomainExtrema(); @@ -178,6 +181,18 @@ define( // Enforce some minimum visible area this.expandRange(); + // Suppress domain changes when pinned + if (this.hasSpecificDomainBounds) { + this.origin[0] = priorDomainOrigin; + this.dimensions[0] = priorDomainDimensions; + if (this.following) { + this.origin[0] = Math.max( + this.domainExtrema[1] - this.dimensions[0], + this.origin[0] + ); + } + } + // ...then enforce a fixed duration if needed if (this.fixedDuration !== undefined) { this.origin[0] = this.origin[0] + this.dimensions[0] - @@ -281,6 +296,22 @@ define( return this.bufferArray; }; + /** + * Set the start and end boundaries (usually time) for the + * domain axis of this updater. + */ + PlotUpdater.prototype.setDomainBounds = function (start, end) { + this.fixedDuration = end - start; + this.origin[0] = start; + this.dimensions[0] = this.fixedDuration; + + // Suppress follow behavior if we have windowed in on the past + this.hasSpecificDomainBounds = true; + this.following = end >= this.domainExtrema[1]; + + this.updateBounds(); + }; + /** * Fill in historical data. */ From d158aa602831555bacf3179ee95f7119be387d69 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 14:04:09 -0700 Subject: [PATCH 25/78] [Plot] Follow time conductor more smoothly WTD-1515 --- platform/features/plot/src/PlotController.js | 2 +- platform/features/plot/src/elements/PlotUpdater.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index be126e2ed2..c8a76e09ec 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -182,7 +182,7 @@ define( this.scheduleUpdate = throttle(function () { self.modeOptions.getModeHandler().getSubPlots() .forEach(updateSubplot); - }, 50); + }); // Subscribe to telemetry when a domain object becomes available $scope.$watch('domainObject', subscribe); diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js index c30786986d..7701f02aa5 100644 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ b/platform/features/plot/src/elements/PlotUpdater.js @@ -308,8 +308,6 @@ define( // Suppress follow behavior if we have windowed in on the past this.hasSpecificDomainBounds = true; this.following = end >= this.domainExtrema[1]; - - this.updateBounds(); }; /** From 77d11e1bcfa26861f71b85cf3479c42f6ffea312 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 14:24:45 -0700 Subject: [PATCH 26/78] [Time Controller] Fix sine wave generation Generate sine wave correctly when start time has been specified, WTD-1515 --- .../generator/src/SinewaveTelemetrySeries.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/example/generator/src/SinewaveTelemetrySeries.js b/example/generator/src/SinewaveTelemetrySeries.js index 9ed2320a4a..930206541f 100644 --- a/example/generator/src/SinewaveTelemetrySeries.js +++ b/example/generator/src/SinewaveTelemetrySeries.js @@ -29,31 +29,34 @@ define( function () { "use strict"; - var firstObservedTime = Date.now(); + var firstObservedTime = Math.floor(Date.now() / 1000); /** * * @constructor */ function SinewaveTelemetrySeries(request) { - var firstTime = (request || {}).start || firstObservedTime, - latestObservedTime = Date.now(), - count = Math.floor((latestObservedTime - firstTime) / 1000), + var latestObservedTime = Math.floor(Date.now() / 1000), + count = latestObservedTime - firstObservedTime, period = request.period || 30, - generatorData = {}; + generatorData = {}, + offset = (request.start !== undefined) ? + Math.floor(request.start / 1000) - firstObservedTime : + 0; generatorData.getPointCount = function () { - return count; + return count - offset; }; generatorData.getDomainValue = function (i, domain) { - return i * 1000 + - (domain !== 'delta' ? firstTime : 0); + return (i + offset) * 1000 + + (domain !== 'delta' ? + (firstObservedTime * 1000) : 0); }; generatorData.getRangeValue = function (i, range) { range = range || "sin"; - return Math[range](i * Math.PI * 2 / period); + return Math[range]((i + offset) * Math.PI * 2 / period); }; return generatorData; From 600ff1a3ee4ca11bf05ca149035ad424fb08b508 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:07:46 -0700 Subject: [PATCH 27/78] [Plot] Requery on event Requery on a query change event from a time conductor, WTD-1515 --- platform/features/plot/src/PlotController.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index c8a76e09ec..a5772b8411 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -65,6 +65,7 @@ define( subPlotFactory = new SubPlotFactory(telemetryFormatter), cachedObjects = [], updater, + lastBounds, handle; // Populate the scope with axis information (specifically, options @@ -139,13 +140,14 @@ define( } // Change the displayable bounds - function setBasePanZoom(event, bounds) { + function setBasePanZoom(unused, bounds) { var start = bounds.start, end = bounds.end; if (updater) { updater.setDomainBounds(start, end); self.update(); } + lastBounds = bounds; } // Create a new subscription; telemetrySubscriber gets @@ -175,6 +177,18 @@ define( } } + // Initiate a new query for data because query bounds changed + function requery() { + if (handle) { + recreateUpdater(); + requestTelemetry(); + // Keep any externally-provided bounds + if (lastBounds) { + setBasePanZoom({}, lastBounds); + } + } + } + this.modeOptions = new PlotModeOptions([], subPlotFactory); this.updateValues = updateValues; @@ -190,6 +204,9 @@ define( // Respond to external bounds changes $scope.$on("telemetry:display:bounds", setBasePanZoom); + // Respond to external query range changes + $scope.$on("telemetry:query:bounds", throttle(requery, 250)); + // Unsubscribe when the plot is destroyed $scope.$on("$destroy", releaseSubscription); From c58ffb4a529d24a2894276aba0fba11a22fecdaa Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:15:09 -0700 Subject: [PATCH 28/78] [Time Controller] Update inner span Update inner span when outer dates change (if needed), WTD-1515 --- .../controllers/TimeConductorController.js | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/platform/commonUI/general/src/controllers/TimeConductorController.js b/platform/commonUI/general/src/controllers/TimeConductorController.js index 26f120c7b0..e440e048cd 100644 --- a/platform/commonUI/general/src/controllers/TimeConductorController.js +++ b/platform/commonUI/general/src/controllers/TimeConductorController.js @@ -69,8 +69,22 @@ define( updateTicks(); } + function updateViewForInnerSpanFromModel(ngModel) { + var span = ngModel.outer[1] - ngModel.outer[0]; + + // Expose readable dates for the knobs + $scope.startInnerText = formatTimestamp(ngModel.inner[0]); + $scope.endInnerText = formatTimestamp(ngModel.inner[1]); + + // And positions for the knobs + $scope.startInnerPct = + toPercent((ngModel.inner[0] - ngModel.outer[0]) / span); + $scope.endInnerPct = + toPercent((ngModel.outer[1] - ngModel.inner[1]) / span); + } + function updateViewFromModel(ngModel) { - var t = now(), span; + var t = now(); ngModel = ngModel || {}; ngModel.outer = ngModel.outer || [ t - 24 * 3600 * 1000, t ]; @@ -80,16 +94,8 @@ define( $scope.startOuterDate = formatTimestamp(ngModel.outer[0]); $scope.endOuterDate = formatTimestamp(ngModel.outer[1]); - // Then readable dates for the knobs - $scope.startInnerText = formatTimestamp(ngModel.inner[0]); - $scope.endInnerText = formatTimestamp(ngModel.inner[1]); - - // And positions for the knobs - span = ngModel.outer[1] - ngModel.outer[0]; - $scope.startInnerPct = - toPercent((ngModel.inner[0] - ngModel.outer[0]) / span); - $scope.endInnerPct = - toPercent((ngModel.outer[1] - ngModel.inner[1]) / span); + // Then various updates for the inner span + updateViewForInnerSpanFromModel(ngModel); // Stick it back is scope (in case we just set defaults) $scope.ngModel = ngModel; @@ -162,11 +168,19 @@ define( function updateOuterStart(text) { var ngModel = $scope.ngModel; ngModel.outer[0] = parseTimestamp(text, ngModel.outer[0]); + ngModel.outer[1] = Math.max(ngModel.outer[0], ngModel.outer[1]); + ngModel.inner[0] = Math.max(ngModel.outer[0], ngModel.inner[0]); + ngModel.inner[1] = Math.max(ngModel.outer[0], ngModel.inner[1]); + updateViewForInnerSpanFromModel(ngModel); } function updateOuterEnd(text) { var ngModel = $scope.ngModel; ngModel.outer[1] = parseTimestamp(text, ngModel.outer[1]); + ngModel.outer[0] = Math.min(ngModel.outer[1], ngModel.outer[0]); + ngModel.inner[0] = Math.min(ngModel.outer[1], ngModel.inner[0]); + ngModel.inner[1] = Math.min(ngModel.outer[1], ngModel.inner[1]); + updateViewForInnerSpanFromModel(ngModel); } $scope.startLeftDrag = startLeftDrag; From b66759e5194e5b21a08132972ff9b250a2b89931 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:31:47 -0700 Subject: [PATCH 29/78] [Plot] Initially establish bounds Initially establish domain bounds with time controller, WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 2 ++ platform/features/plot/src/PlotController.js | 32 ++++++++++--------- .../features/plot/src/elements/PlotUpdater.js | 3 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 2583c50b14..f0a32c69a0 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -92,6 +92,8 @@ define( conductorScope.$watch('conductor.outer[1]', updateConductorOuter); conductorScope.$watch('conductor.inner[0]', updateConductorInner); conductorScope.$watch('conductor.inner[1]', updateConductorInner); + + repScope.$on('telemetry:view', updateConductorInner); } // Handle a specific representation of a specific domain object diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index a5772b8411..3b846d33b8 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -95,6 +95,17 @@ define( } } + // Change the displayable bounds + function setBasePanZoom(unused, bounds) { + var start = bounds.start, + end = bounds.end; + if (updater) { + updater.setDomainBounds(start, end); + self.update(); + } + lastBounds = bounds; + } + // Reinstantiate the plot updater (e.g. because we have a // new subscription.) This will clear the plot. function recreateUpdater() { @@ -108,6 +119,10 @@ define( handle, ($scope.axes[1].active || {}).key ); + // Keep any externally-provided bounds + if (lastBounds) { + setBasePanZoom({}, lastBounds); + } } // Handle new telemetry data in this plot @@ -139,17 +154,6 @@ define( } } - // Change the displayable bounds - function setBasePanZoom(unused, bounds) { - var start = bounds.start, - end = bounds.end; - if (updater) { - updater.setDomainBounds(start, end); - self.update(); - } - lastBounds = bounds; - } - // Create a new subscription; telemetrySubscriber gets // to do the meaningful work here. function subscribe(domainObject) { @@ -182,10 +186,6 @@ define( if (handle) { recreateUpdater(); requestTelemetry(); - // Keep any externally-provided bounds - if (lastBounds) { - setBasePanZoom({}, lastBounds); - } } } @@ -210,6 +210,8 @@ define( // Unsubscribe when the plot is destroyed $scope.$on("$destroy", releaseSubscription); + // Notify any external observers that a new telemetry view is here + $scope.$emit("telemetry:view"); } /** diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js index 7701f02aa5..d4b4ad3eec 100644 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ b/platform/features/plot/src/elements/PlotUpdater.js @@ -307,7 +307,8 @@ define( // Suppress follow behavior if we have windowed in on the past this.hasSpecificDomainBounds = true; - this.following = end >= this.domainExtrema[1]; + this.following = + !this.domainExtrema || (end >= this.domainExtrema[1]); }; /** From 142af3db777bdfae9fef3bb89cc3aed2e74d9295 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:44:18 -0700 Subject: [PATCH 30/78] [Time Controller] Add JSDoc WTD-1515 --- .../src/ConductorCapabilityDecorator.js | 11 +++++ .../conductor/src/ConductorRepresenter.js | 8 +++- .../conductor/src/ConductorService.js | 15 +++++++ .../src/ConductorTelemetryCapability.js | 13 ++++++ .../features/conductor/src/TimeConductor.js | 45 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorCapabilityDecorator.js index 85b34152f3..7c6ba46133 100644 --- a/platform/features/conductor/src/ConductorCapabilityDecorator.js +++ b/platform/features/conductor/src/ConductorCapabilityDecorator.js @@ -26,6 +26,17 @@ define( function (ConductorTelemetryCapability) { 'use strict'; + /** + * Decorates the `capabilityService` such that any exposed `telemetry` + * capabilities have their requests mediated by the time conductor. + * + * @constructor + * @memberof platform/features/conductor + * @implements {CapabilityService} + * @param {platform/features/conductor.ConductorService} conductorServe + * the service which exposes the global time conductor + * @param {CapabilityService} capabilityService the decorated service + */ function ConductorCapabilityDecorator(conductorService, capabilityService) { this.conductorService = conductorService; this.capabilityService = capabilityService; diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index f0a32c69a0..0617c771b1 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -42,9 +42,15 @@ define( * The ConductorRepresenter attaches the universal time conductor * to views. * - * @memberof platform/commonUI/edit * @implements {Representer} * @constructor + * @memberof platform/features/conductor + * @param {platform/features/conductor.ConductorService} conductorService + * service which provides the active time conductor + * @param $compile Angular's $compile + * @param {ViewDefinition[]} views all defined views + * @param {Scope} the scope of the representation + * @param element the jqLite-wrapped representation element */ function ConductorRepresenter(conductorService, $compile, views, scope, element) { var conductorScope; diff --git a/platform/features/conductor/src/ConductorService.js b/platform/features/conductor/src/ConductorService.js index c40838ff53..59cfa95e3c 100644 --- a/platform/features/conductor/src/ConductorService.js +++ b/platform/features/conductor/src/ConductorService.js @@ -29,6 +29,16 @@ define( var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24, SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4; + /** + * Provides a single global instance of the time conductor, which + * controls both query ranges and displayed ranges for telemetry + * data. + * + * @constructor + * @memberof platform/features/conductor + * @param {Function} now a function which returns the current time + * as a UNIX timestamp, in milliseconds + */ function ConductorService(now) { var initialEnd = Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS; @@ -37,6 +47,11 @@ define( new TimeConductor(initialEnd - ONE_DAY_IN_MS, initialEnd); } + /** + * Get the global instance of the time conductor. + * @returns {platform/features/conductor.TimeConductor} the + * time conductor + */ ConductorService.prototype.getConductor = function () { return this.conductor; }; diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js index 02950ac8f2..5d224b0415 100644 --- a/platform/features/conductor/src/ConductorTelemetryCapability.js +++ b/platform/features/conductor/src/ConductorTelemetryCapability.js @@ -26,6 +26,19 @@ define( function () { 'use strict'; + /** + * Wrapper for the `telemetry` capability which adds start/end + * times to all requests based on the current state of a time + * conductor. + * + * @constructor + * @memberof platform/features/conductor + * @augments {platform/telemetry.TelemetryCapability} + * @param {platform/features/conductor.TimeConductor} timeConductor + * the time conductor which controls these queries + * @param {platform/telemetry.TelemetryCapability} telemetryCapability + * the wrapped capability + */ function ConductorTelemetryCapability(timeConductor, telemetryCapability) { this.timeConductor = timeConductor; this.wrappedCapability = telemetryCapability; diff --git a/platform/features/conductor/src/TimeConductor.js b/platform/features/conductor/src/TimeConductor.js index d4eefeffd8..eeb104839a 100644 --- a/platform/features/conductor/src/TimeConductor.js +++ b/platform/features/conductor/src/TimeConductor.js @@ -21,15 +21,44 @@ *****************************************************************************/ /*global define*/ +/** + * The time conductor bundle adds a global control to the bottom of the + * outermost viewing area. This controls both the range for time-based + * queries and for time-based displays. + * + * @namespace platform/features/conductor + */ define( function () { 'use strict'; + /** + * Wrapper for the `telemetry` capability which adds start/end + * times to all requests based on the current state of a time + * conductor. + * + * Note that both start and end times are in units which may + * vary depending on the domains of telemetry being used. Most + * commonly, these are UNIX timestamps in milliseconds. + * + * @memberof platform/features/conductor + * @constructor + * @augments {platform/telemetry.TelemetryCapability} + * @param {platform/features/conductor.TimeConductor} timeConductor + * the time conductor which controls these queries + * @param {platform/telemetry.TelemetryCapability} telemetryCapability + * the wrapped capability + */ function TimeConductor(start, end) { this.inner = [ start, end ]; this.outer = [ start, end ]; } + /** + * Get or set (if called with an argument) the start time for queries. + * @param {number} [value] the start time to set + * @returns {number} the start time + */ TimeConductor.prototype.queryStart = function (value) { if (arguments.length > 0) { this.outer[0] = value; @@ -37,6 +66,11 @@ define( return this.outer[0]; }; + /** + * Get or set (if called with an argument) the end time for queries. + * @param {number} [value] the end time to set + * @returns {number} the end time + */ TimeConductor.prototype.queryEnd = function (value) { if (arguments.length > 0) { this.outer[1] = value; @@ -44,6 +78,12 @@ define( return this.outer[1]; }; + + /** + * Get or set (if called with an argument) the start time for displays. + * @param {number} [value] the start time to set + * @returns {number} the start time + */ TimeConductor.prototype.displayStart = function (value) { if (arguments.length > 0) { this.inner[0] = value; @@ -51,6 +91,11 @@ define( return this.inner[0]; }; + /** + * Get or set (if called with an argument) the end time for displays. + * @param {number} [value] the end time to set + * @returns {number} the end time + */ TimeConductor.prototype.displayEnd = function (value) { if (arguments.length > 0) { this.inner[1] = value; From 4d276888e1f806d188196bbbdd68947694425acd Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:53:55 -0700 Subject: [PATCH 31/78] [Plot] Update failining spec WTD-1515 --- platform/features/plot/test/PlotControllerSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index e6c79b4e54..e9fec60aaf 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -49,7 +49,7 @@ define( beforeEach(function () { mockScope = jasmine.createSpyObj( "$scope", - [ "$watch", "$on" ] + [ "$watch", "$on", "$emit" ] ); mockFormatter = jasmine.createSpyObj( "formatter", From 29c460556aa61c6becb6cd330a8aaf95b15890cb Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 4 Sep 2015 15:59:38 -0700 Subject: [PATCH 32/78] [Representers] Destroy representers Invoke the destroy methods of any active representers when a scope is destroyed; supports time controller, which needs to accurately track when it has or hasn't been attached to a view. WTD-1515 --- .../representation/src/MCTRepresentation.js | 18 ++++++++++++++---- .../test/MCTRepresentationSpec.js | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/platform/representation/src/MCTRepresentation.js b/platform/representation/src/MCTRepresentation.js index 49b2ae0f57..98a814c362 100644 --- a/platform/representation/src/MCTRepresentation.js +++ b/platform/representation/src/MCTRepresentation.js @@ -136,6 +136,14 @@ define( } } + // Destroy (deallocate any resources associated with) any + // active representers. + function destroyRepresenters() { + activeRepresenters.forEach(function (activeRepresenter) { + activeRepresenter.destroy(); + }); + } + // General-purpose refresh mechanism; should set up the scope // as appropriate for current representation key and // domain object. @@ -152,10 +160,8 @@ define( // via the "inclusion" field $scope.inclusion = representation && getPath(representation); - // Any existing gestures are no longer valid; release them. - activeRepresenters.forEach(function (activeRepresenter) { - activeRepresenter.destroy(); - }); + // Any existing representers are no longer valid; release them. + destroyRepresenters(); // Log if a key was given, but no matching representation // was found. @@ -209,6 +215,10 @@ define( // model's "modified" field, by the mutation capability. $scope.$watch("domainObject.getModel().modified", refreshCapabilities); + // Make sure any resources allocated by representers also get + // released. + $scope.$on("$destroy", destroyRepresenters); + // Do one initial refresh, so that we don't need another // digest iteration just to populate the scope. Failure to // do this can result in unstable digest cycles, which diff --git a/platform/representation/test/MCTRepresentationSpec.js b/platform/representation/test/MCTRepresentationSpec.js index 337f214a86..a50347df70 100644 --- a/platform/representation/test/MCTRepresentationSpec.js +++ b/platform/representation/test/MCTRepresentationSpec.js @@ -106,7 +106,7 @@ define( mockSce.trustAsResourceUrl.andCallFake(function (url) { return url; }); - mockScope = jasmine.createSpyObj("scope", [ "$watch" ]); + mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]); mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); From 47b97a504ea5c16d8b571ecdfab74eabbb920056 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 16:28:01 -0700 Subject: [PATCH 33/78] [Telemetry] Document TelemetryRequest Document TelemetryRequest to record new parameters in support of the time conductor, WTD-1515 --- platform/telemetry/src/TelemetryAggregator.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/platform/telemetry/src/TelemetryAggregator.js b/platform/telemetry/src/TelemetryAggregator.js index fb4cf81fe0..86257befb7 100644 --- a/platform/telemetry/src/TelemetryAggregator.js +++ b/platform/telemetry/src/TelemetryAggregator.js @@ -31,6 +31,30 @@ define( function () { "use strict"; + /** + * Describes a request for telemetry data. Note that responses + * may contain either a sub- or superset of the requested data. + * @typedef TelemetryRequest + * @property {string} source an identifier for the relevant + * source of telemetry data + * @property {string} key an identifier for the specific + * series of telemetry data provided by that source + * @property {number} [start] the earliest domain value of + * interest for that telemetry data; for time-based + * domains, this is in milliseconds since the start + * of 1970 + * @property {number} [end] the latest domain value of interest + * for that telemetry data; for time-based domains, + * this is in milliseconds since 1970 + * @property {string} [domain] the domain for the query; if + * omitted, this will be whatever the "normal" + * domain is for a given telemetry series (the + * first domain from its metadata) + * @property {number} [size] if set, indicates the maximum number + * of data points of interest for this request (more + * recent domain values will be preferred) + */ + /** * Request telemetry data. * @param {TelemetryRequest[]} requests and array of From c026bfa17da75e29bc6d1dd073cd6769fa2872b8 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 16:37:10 -0700 Subject: [PATCH 34/78] [Time Conductor] Begin adding support to fixed position Begin adding support for universal time controller to fixed position view, WTD-1515. --- platform/features/layout/bundle.json | 3 +- .../features/layout/src/FixedController.js | 39 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/platform/features/layout/bundle.json b/platform/features/layout/bundle.json index f1006d67e8..8a8d158e32 100644 --- a/platform/features/layout/bundle.json +++ b/platform/features/layout/bundle.json @@ -168,7 +168,8 @@ "$q", "dialogService", "telemetrySubscriber", - "telemetryFormatter" + "telemetryFormatter", + "throttle" ] } ], diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 410c0d2f94..1fa057642a 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -38,9 +38,9 @@ define( * @constructor * @param {Scope} $scope the controller's Angular scope */ - function FixedController($scope, $q, dialogService, telemetrySubscriber, telemetryFormatter) { + function FixedController($scope, $q, dialogService, telemetryHandler, telemetryFormatter, throttle) { var self = this, - subscription, + handle, names = {}, // Cache names by ID values = {}, // Cache values by ID elementProxiesById = {}; @@ -87,14 +87,14 @@ define( limit = telemetryObject && telemetryObject.getCapability('limit'), datum = telemetryObject && - subscription.getDatum(telemetryObject), + handle.getDatum(telemetryObject), alarm = limit && datum && limit.evaluate(datum); if (id) { (elementProxiesById[id] || []).forEach(function (element) { names[id] = telemetryObject.getModel().name; values[id] = telemetryFormatter.formatRangeValue( - subscription.getRangeValue(telemetryObject) + handle.getRangeValue(telemetryObject) ); element.name = names[id]; element.value = values[id]; @@ -115,8 +115,8 @@ define( // Update telemetry values based on new data available function updateValues() { - if (subscription) { - subscription.getTelemetryObjects().forEach(updateValue); + if (handle) { + handle.getTelemetryObjects().forEach(updateValue); } } @@ -178,22 +178,24 @@ define( // Free up subscription to telemetry function releaseSubscription() { - if (subscription) { - subscription.unsubscribe(); - subscription = undefined; + if (handle) { + handle.unsubscribe(); + handle = undefined; } } // Subscribe to telemetry updates for this domain object function subscribe(domainObject) { // Release existing subscription (if any) - if (subscription) { - subscription.unsubscribe(); + if (handle) { + handle.unsubscribe(); } // Make a new subscription - subscription = domainObject && - telemetrySubscriber.subscribe(domainObject, updateValues); + handle = domainObject && telemetryHandler.handle( + domainObject, + updateValues + ); } // Handle changes in the object's composition @@ -204,6 +206,11 @@ define( subscribe($scope.domainObject); } + // Trigger a new query for telemetry data + function requery() { + subscribe($scope.domainObject); + } + // Add an element to this view function addElement(element) { // Ensure that configuration field is populated @@ -277,6 +284,12 @@ define( // Position panes where they are dropped $scope.$on("mctDrop", handleDrop); + + // Respond to external bounds changes + $scope.$on("telemetry:display:bounds", throttle(requery, 250)); + + // Respond to external query range changes + $scope.$on("telemetry:query:bounds", throttle(requery, 250)); } /** From 760f4b818f2f152d08b04cb2221ede22cc282d70 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 16:53:06 -0700 Subject: [PATCH 35/78] [Time Conductor] Update fixed position from history WTD-1515 --- .../features/layout/src/FixedController.js | 76 +++++++++++++------ 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 1fa057642a..9d1b4f900f 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -43,7 +43,8 @@ define( handle, names = {}, // Cache names by ID values = {}, // Cache values by ID - elementProxiesById = {}; + elementProxiesById = {}, + maxDomainValue = Number.POSITIVE_INFINITY; // Convert from element x/y/width/height to an // appropriate ng-style argument, to position elements. @@ -81,25 +82,51 @@ define( return element.handles().map(generateDragHandle); } - // Update the displayed value for this object - function updateValue(telemetryObject) { - var id = telemetryObject && telemetryObject.getId(), + // Update the value displayed in elements of this telemetry object + function setDisplayedValue(telemetryObject, value, alarm) { + var id = telemetryObject.getId(); + (elementProxiesById[id] || []).forEach(function (element) { + names[id] = telemetryObject.getModel().name; + values[id] = telemetryFormatter.formatRangeValue(value); + element.name = names[id]; + element.value = values[id]; + element.cssClass = alarm && alarm.cssClass; + }); + } + + // Update the displayed value for this object, from a specific + // telemetry series + function updateValueFromSeries(telemetryObject, telemetrySeries) { + var index = telemetrySeries.getPointCount() - 1, limit = telemetryObject && telemetryObject.getCapability('limit'), - datum = telemetryObject && - handle.getDatum(telemetryObject), - alarm = limit && datum && limit.evaluate(datum); + datum = telemetryObject && handle.getDatum( + telemetryObject, + telemetrySeries, + index + ); - if (id) { - (elementProxiesById[id] || []).forEach(function (element) { - names[id] = telemetryObject.getModel().name; - values[id] = telemetryFormatter.formatRangeValue( - handle.getRangeValue(telemetryObject) - ); - element.name = names[id]; - element.value = values[id]; - element.cssClass = alarm && alarm.cssClass; - }); + setDisplayedValue( + telemetryObject, + telemetrySeries.getRangeValue(index), + limit && datum && limit.evaluate(datum) + ); + } + + // Update the displayed value for this object + function updateValue(telemetryObject) { + var limit = telemetryObject && + telemetryObject.getCapability('limit'), + datum = telemetryObject && + handle.getDatum(telemetryObject); + + if (telemetryObject && + handle.getDomainValue(telemetryObject) < maxDomainValue) { + setDisplayedValue( + telemetryObject, + handle.getRangeValue(telemetryObject), + limit && datum && limit.evaluate(datum) + ); } } @@ -207,8 +234,14 @@ define( } // Trigger a new query for telemetry data - function requery() { - subscribe($scope.domainObject); + function updateDisplayBounds(bounds) { + maxDomainValue = bounds.end; + if (handle) { + handle.request( + { size: 1 }, // Only need a single data point + updateValueFromSeries + ); + } } // Add an element to this view @@ -286,10 +319,7 @@ define( $scope.$on("mctDrop", handleDrop); // Respond to external bounds changes - $scope.$on("telemetry:display:bounds", throttle(requery, 250)); - - // Respond to external query range changes - $scope.$on("telemetry:query:bounds", throttle(requery, 250)); + $scope.$on("telemetry:display:bounds", updateDisplayBounds); } /** From 351181d38eec032ff7f66d8b54065c5da6e3181d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 16:58:15 -0700 Subject: [PATCH 36/78] [Time Controller] Allow datum retrieval from histories WTD-1515 --- platform/telemetry/src/TelemetryHandle.js | 9 +++++++-- platform/telemetry/src/TelemetrySubscription.js | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/platform/telemetry/src/TelemetryHandle.js b/platform/telemetry/src/TelemetryHandle.js index 145edfc5d7..1061c6386b 100644 --- a/platform/telemetry/src/TelemetryHandle.js +++ b/platform/telemetry/src/TelemetryHandle.js @@ -79,8 +79,7 @@ define( /** * Change the request duration. - * @param {object|number} request the duration of historical - * data to look at; or, the request to issue + * @param {TelemetryRequest} request the request to issue * @param {Function} [callback] a callback that will be * invoked as new data becomes available, with the * domain object for which new data is available. @@ -107,6 +106,12 @@ define( .then(issueRequests); }; + self.getDatum = function (telemetryObject, series, index) { + return arguments.length > 1 ? + subscription.getDatum(telemetryObject) : + subscription.makeDatum(telemetryObject, series, index); + }; + return self; } diff --git a/platform/telemetry/src/TelemetrySubscription.js b/platform/telemetry/src/TelemetrySubscription.js index 8b4d7d7a9c..93539d1ab0 100644 --- a/platform/telemetry/src/TelemetrySubscription.js +++ b/platform/telemetry/src/TelemetrySubscription.js @@ -239,6 +239,10 @@ define( initialize(); this.unlistenToMutation = addMutationListener(); + + // Expose makeDatum for TelemetryHandler to use, but not + // as part of public API + this.makeDatum = makeDatum; } TelemetrySubscription.prototype.unsubscribeAll = function () { From d2dfec3ce7338fdf02359bc6342ab820d525cdca Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 17:03:58 -0700 Subject: [PATCH 37/78] [Time Controller] Simplify retrieval of datum objects ...for historical data. Supports WTD-1515 --- .../features/layout/src/FixedController.js | 1 - platform/telemetry/src/TelemetryHandle.js | 20 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 9d1b4f900f..9482f71aba 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -102,7 +102,6 @@ define( telemetryObject.getCapability('limit'), datum = telemetryObject && handle.getDatum( telemetryObject, - telemetrySeries, index ); diff --git a/platform/telemetry/src/TelemetryHandle.js b/platform/telemetry/src/TelemetryHandle.js index 1061c6386b..ae25fd9bfa 100644 --- a/platform/telemetry/src/TelemetryHandle.js +++ b/platform/telemetry/src/TelemetryHandle.js @@ -106,10 +106,24 @@ define( .then(issueRequests); }; - self.getDatum = function (telemetryObject, series, index) { - return arguments.length > 1 ? + /** + * Get the latest telemetry datum for this domain object. This + * will be from real-time telemetry, unless an index is specified, + * in which case it will be pulled from the historical telemetry + * series at the specified index. + * + * @param {DomainObject} domainObject the object of interest + * @param {number} [index] the index of the data of interest + * @returns {TelemetryDatum} the most recent datum + */ + self.getDatum = function (telemetryObject, index) { + return typeof index !== 'number' ? subscription.getDatum(telemetryObject) : - subscription.makeDatum(telemetryObject, series, index); + subscription.makeDatum( + telemetryObject, + this.getSeries(telemetryObject), + index + ); }; return self; From fdbc91131bdff31b3467bfe2d20c3be5278c5d7d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 8 Sep 2015 17:18:39 -0700 Subject: [PATCH 38/78] [Time Controller] Update bundle definition ...for Fixed Position view to reflect changes to dependencies, WTD-1515. --- platform/features/layout/bundle.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/layout/bundle.json b/platform/features/layout/bundle.json index 8a8d158e32..0fd8b9dea4 100644 --- a/platform/features/layout/bundle.json +++ b/platform/features/layout/bundle.json @@ -167,7 +167,7 @@ "$scope", "$q", "dialogService", - "telemetrySubscriber", + "telemetryHandler", "telemetryFormatter", "throttle" ] From a4dda695dde28ae5d338961303678645d09ded50 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 9 Sep 2015 16:46:00 -0700 Subject: [PATCH 39/78] [Time Controller] Get conductor working with fixed pos. WTD-1515 --- .../generator/src/SinewaveTelemetrySeries.js | 12 +++++++--- .../src/ConductorTelemetryCapability.js | 23 ++++++++++++++++--- .../features/layout/src/FixedController.js | 4 ++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/example/generator/src/SinewaveTelemetrySeries.js b/example/generator/src/SinewaveTelemetrySeries.js index 930206541f..5b7914a867 100644 --- a/example/generator/src/SinewaveTelemetrySeries.js +++ b/example/generator/src/SinewaveTelemetrySeries.js @@ -37,21 +37,27 @@ define( */ function SinewaveTelemetrySeries(request) { var latestObservedTime = Math.floor(Date.now() / 1000), - count = latestObservedTime - firstObservedTime, + endTime = (request.end !== undefined) ? + Math.floor(request.end / 1000) : latestObservedTime, + count = + Math.min(endTime, latestObservedTime) - firstObservedTime, period = request.period || 30, generatorData = {}, offset = (request.start !== undefined) ? Math.floor(request.start / 1000) - firstObservedTime : 0; + if (request.size !== undefined) { + offset = Math.max(offset, count - request.size); + } + generatorData.getPointCount = function () { return count - offset; }; generatorData.getDomainValue = function (i, domain) { return (i + offset) * 1000 + - (domain !== 'delta' ? - (firstObservedTime * 1000) : 0); + (domain !== 'delta' ? (firstObservedTime * 1000) : 0); }; generatorData.getRangeValue = function (i, range) { diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js index 5d224b0415..ca9a18a24b 100644 --- a/platform/features/conductor/src/ConductorTelemetryCapability.js +++ b/platform/features/conductor/src/ConductorTelemetryCapability.js @@ -44,18 +44,35 @@ define( this.wrappedCapability = telemetryCapability; } + ConductorTelemetryCapability.prototype.amendRequest = function (request) { + request = request || {}; + + // This isn't really the right check, but it happens to distinguish + // plots (which want to query for the full set of data for easy + // panning) from views like fixed position, which only want the + // single latest data point. + if (request.size !== undefined) { + request.start = this.timeConductor.displayStart(); + request.end = this.timeConductor.displayEnd(); + } else { + request.start = this.timeConductor.queryStart(); + request.end = this.timeConductor.queryEnd(); + } + + return request; + }; + ConductorTelemetryCapability.prototype.getMetadata = function () { return this.wrappedCapability.getMetadata(); }; ConductorTelemetryCapability.prototype.requestData = function (request) { - request = request || {}; - request.start = this.timeConductor.queryStart(); - request.end = this.timeConductor.queryEnd(); + request = this.amendRequest(request); return this.wrappedCapability.requestData(request); }; ConductorTelemetryCapability.prototype.subscribe = function (callback, request) { + request = this.amendRequest(request); return this.wrappedCapability.subscribe(callback, request); }; diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 9482f71aba..fd446bcb20 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -120,7 +120,7 @@ define( handle.getDatum(telemetryObject); if (telemetryObject && - handle.getDomainValue(telemetryObject) < maxDomainValue) { + (handle.getDomainValue(telemetryObject) < maxDomainValue)) { setDisplayedValue( telemetryObject, handle.getRangeValue(telemetryObject), @@ -233,7 +233,7 @@ define( } // Trigger a new query for telemetry data - function updateDisplayBounds(bounds) { + function updateDisplayBounds(event, bounds) { maxDomainValue = bounds.end; if (handle) { handle.request( From e3b191b5dce459ba6718271f115e895d563ca770 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 9 Sep 2015 16:52:46 -0700 Subject: [PATCH 40/78] [Time Controller] Update failing specs Update failing specs to reflect support for time conductor, WTD-1515 --- .../test/ConductorTelemetryCapabilitySpec.js | 8 +++- .../layout/test/FixedControllerSpec.js | 45 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js index e5c9066e22..2cc30229bc 100644 --- a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js +++ b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js @@ -83,7 +83,7 @@ define( }); }); - it("simply delegates subscribe calls", function () { + it("adds start/end times to subscribe calls", function () { var mockCallback = jasmine.createSpy('callback'), testRequest = { someKey: "some value" }; expect(conductorTelemetryCapability.subscribe( @@ -92,7 +92,11 @@ define( )).toBe(mockUnsubscribe); expect(mockTelemetryCapability.subscribe).toHaveBeenCalledWith( mockCallback, - { someKey: "some value" } + { + someKey: "some value", + start: testStartTime, + end: testEndTime + } ); }); diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index b95bbc9eb5..95b9842a4c 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -30,10 +30,10 @@ define( var mockScope, mockQ, mockDialogService, - mockSubscriber, + mockHandler, mockFormatter, mockDomainObject, - mockSubscription, + mockHandle, mockEvent, testGrid, testModel, @@ -78,9 +78,9 @@ define( '$scope', [ "$on", "$watch", "commit" ] ); - mockSubscriber = jasmine.createSpyObj( - 'telemetrySubscriber', - [ 'subscribe' ] + mockHandler = jasmine.createSpyObj( + 'telemetryHandler', + [ 'handle' ] ); mockQ = jasmine.createSpyObj('$q', ['when']); mockDialogService = jasmine.createSpyObj( @@ -95,9 +95,15 @@ define( 'domainObject', [ 'getId', 'getModel', 'getCapability' ] ); - mockSubscription = jasmine.createSpyObj( + mockHandle = jasmine.createSpyObj( 'subscription', - [ 'unsubscribe', 'getTelemetryObjects', 'getRangeValue', 'getDatum' ] + [ + 'unsubscribe', + 'getDomainValue', + 'getTelemetryObjects', + 'getRangeValue', + 'getDatum' + ] ); mockEvent = jasmine.createSpyObj( 'event', @@ -116,13 +122,14 @@ define( { type: "fixed.telemetry", id: 'c', x: 1, y: 1 } ]}; - mockSubscriber.subscribe.andReturn(mockSubscription); - mockSubscription.getTelemetryObjects.andReturn( + mockHandler.handle.andReturn(mockHandle); + mockHandle.getTelemetryObjects.andReturn( testModel.composition.map(makeMockDomainObject) ); - mockSubscription.getRangeValue.andCallFake(function (o) { + mockHandle.getRangeValue.andCallFake(function (o) { return testValues[o.getId()]; }); + mockHandle.getDomainValue.andReturn(12321); mockFormatter.formatRangeValue.andCallFake(function (v) { return "Formatted " + v; }); @@ -137,7 +144,7 @@ define( mockScope, mockQ, mockDialogService, - mockSubscriber, + mockHandler, mockFormatter ); }); @@ -145,7 +152,7 @@ define( it("subscribes when a domain object is available", function () { mockScope.domainObject = mockDomainObject; findWatch("domainObject")(mockDomainObject); - expect(mockSubscriber.subscribe).toHaveBeenCalledWith( + expect(mockHandler.handle).toHaveBeenCalledWith( mockDomainObject, jasmine.any(Function) ); @@ -156,13 +163,13 @@ define( // First pass - should simply should subscribe findWatch("domainObject")(mockDomainObject); - expect(mockSubscription.unsubscribe).not.toHaveBeenCalled(); - expect(mockSubscriber.subscribe.calls.length).toEqual(1); + expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); + expect(mockHandler.handle.calls.length).toEqual(1); // Object changes - should unsubscribe then resubscribe findWatch("domainObject")(mockDomainObject); - expect(mockSubscription.unsubscribe).toHaveBeenCalled(); - expect(mockSubscriber.subscribe.calls.length).toEqual(2); + expect(mockHandle.unsubscribe).toHaveBeenCalled(); + expect(mockHandler.handle.calls.length).toEqual(2); }); it("exposes visible elements based on configuration", function () { @@ -255,7 +262,7 @@ define( findWatch("model.composition")(mockScope.model.composition); // Invoke the subscription callback - mockSubscriber.subscribe.mostRecentCall.args[1](); + mockHandler.handle.mostRecentCall.args[1](); // Get elements that controller is now exposing elements = controller.getElements(); @@ -333,11 +340,11 @@ define( // Make an object available findWatch('domainObject')(mockDomainObject); // Also verify precondition - expect(mockSubscription.unsubscribe).not.toHaveBeenCalled(); + expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); // Destroy the scope findOn('$destroy')(); // Should have unsubscribed - expect(mockSubscription.unsubscribe).toHaveBeenCalled(); + expect(mockHandle.unsubscribe).toHaveBeenCalled(); }); it("exposes its grid size", function () { From 2ec9956d44a43ba6dcbe2d235852d08117d9c048 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 10:16:28 -0700 Subject: [PATCH 41/78] [Time Conductor] Incorporate feedback from code review Retain reference to scope in ConductorRepresenter directly via this, instead of revealing via a closure-bound function. This approach is not necessary to avoid https://docs.angularjs.org/error/ng/cpws in this circumstance. WTD-1515 --- .../conductor/src/ConductorRepresenter.js | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/platform/features/conductor/src/ConductorRepresenter.js b/platform/features/conductor/src/ConductorRepresenter.js index 0617c771b1..f858810db1 100644 --- a/platform/features/conductor/src/ConductorRepresenter.js +++ b/platform/features/conductor/src/ConductorRepresenter.js @@ -53,22 +53,15 @@ define( * @param element the jqLite-wrapped representation element */ function ConductorRepresenter(conductorService, $compile, views, scope, element) { - var conductorScope; - - // Angular doesn't like objects to retain references to scopes - this.getScope = function () { - return scope; - }; - this.conductorScope = function (s) { - return (conductorScope = arguments.length > 0 ? s : conductorScope); - }; - + this.scope = scope; this.conductorService = conductorService; this.element = element; this.views = views; this.$compile = $compile; } + + // Update the time conductor from the scope function wireScope(conductor, conductorScope, repScope) { function updateConductorOuter() { @@ -102,6 +95,11 @@ define( repScope.$on('telemetry:view', updateConductorInner); } + ConductorRepresenter.prototype.conductorScope = function (s) { + return (this.cScope = arguments.length > 0 ? + s : this.cScope); + }; + // Handle a specific representation of a specific domain object ConductorRepresenter.prototype.represent = function represent(representation, representedObject) { this.destroy(); @@ -112,11 +110,11 @@ define( this.hadAbs = this.element.hasClass('abs'); // Create a new scope for the conductor - this.conductorScope(this.getScope().$new()); + this.conductorScope(this.scope.$new()); wireScope( this.conductorService.getConductor(), this.conductorScope(), - this.getScope() + this.scope ); this.conductorElement = this.$compile(TEMPLATE)(this.conductorScope()); From 4c79c9a1b1af682c1f44890d5d3cb37b2124734c Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 10:21:21 -0700 Subject: [PATCH 42/78] [Time Conductor] Clarify start/end naming WTD-1515 --- .../features/conductor/src/TimeConductor.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/platform/features/conductor/src/TimeConductor.js b/platform/features/conductor/src/TimeConductor.js index eeb104839a..ec4200ed6e 100644 --- a/platform/features/conductor/src/TimeConductor.js +++ b/platform/features/conductor/src/TimeConductor.js @@ -50,8 +50,8 @@ define( * the wrapped capability */ function TimeConductor(start, end) { - this.inner = [ start, end ]; - this.outer = [ start, end ]; + this.inner = { start: start, end: end }; + this.outer = { start: start, end: end }; } /** @@ -61,9 +61,9 @@ define( */ TimeConductor.prototype.queryStart = function (value) { if (arguments.length > 0) { - this.outer[0] = value; + this.outer.start = value; } - return this.outer[0]; + return this.outer.start; }; /** @@ -73,9 +73,9 @@ define( */ TimeConductor.prototype.queryEnd = function (value) { if (arguments.length > 0) { - this.outer[1] = value; + this.outer.end = value; } - return this.outer[1]; + return this.outer.end; }; @@ -86,9 +86,9 @@ define( */ TimeConductor.prototype.displayStart = function (value) { if (arguments.length > 0) { - this.inner[0] = value; + this.inner.start = value; } - return this.inner[0]; + return this.inner.start; }; /** @@ -98,9 +98,9 @@ define( */ TimeConductor.prototype.displayEnd = function (value) { if (arguments.length > 0) { - this.inner[1] = value; + this.inner.end = value; } - return this.inner[1]; + return this.inner.end; }; return TimeConductor; From 78fae345da4f7c4661a10dfb8a4e5e7f2ca174bf Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 10:54:44 -0700 Subject: [PATCH 43/78] [Time Conductor] Rename TimeConductorController Rename TimeConductorController to TimeRangeController, to reflect that this is intended to serve as a more general control. Additionally, stop using arrays for inner and outer bounds and instead use explicit start/end properties, for clarity. WTD-1515 --- platform/commonUI/general/bundle.json | 4 +- .../templates/controls/time-controller.html | 2 +- ...orController.js => TimeRangeController.js} | 96 +++++++++++-------- .../conductor/src/ConductorRepresenter.js | 45 +++++---- 4 files changed, 87 insertions(+), 60 deletions(-) rename platform/commonUI/general/src/controllers/{TimeConductorController.js => TimeRangeController.js} (69%) diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 73de6ba4a2..374839e010 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -58,8 +58,8 @@ ], "controllers": [ { - "key": "TimeConductorController", - "implementation": "controllers/TimeConductorController.js", + "key": "TimeRangeController", + "implementation": "controllers/TimeRangeController.js", "depends": [ "$scope", "now" ] }, { diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 5f60741672..6b8f3e4fc8 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -22,7 +22,7 @@ properly on the range left and right bounds. --> -
+
Start: Date: Thu, 10 Sep 2015 11:20:09 -0700 Subject: [PATCH 44/78] [Time Conductor] Clean up code style Clean up code style in TelemetrySubscription, for changes associated with WTD-1515. --- .../telemetry/src/TelemetrySubscription.js | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/platform/telemetry/src/TelemetrySubscription.js b/platform/telemetry/src/TelemetrySubscription.js index 93539d1ab0..5dcab54b94 100644 --- a/platform/telemetry/src/TelemetrySubscription.js +++ b/platform/telemetry/src/TelemetrySubscription.js @@ -123,25 +123,6 @@ define( telemetryCapability.getMetadata(); } - // From a telemetry series, retrieve a single data point - // containing all fields for domains/ranges - function makeDatum(domainObject, series, index) { - var metadata = lookupMetadata(domainObject), - result = {}; - - (metadata.domains || []).forEach(function (domain) { - result[domain.key] = - series.getDomainValue(index, domain.key); - }); - - (metadata.ranges || []).forEach(function (range) { - result[range.key] = - series.getRangeValue(index, range.key); - }); - - return result; - } - // Update the latest telemetry data for a specific // domain object. This will notify listeners. function update(domainObject, series) { @@ -160,7 +141,7 @@ define( pool.put(domainObject.getId(), { domain: series.getDomainValue(count - 1), range: series.getRangeValue(count - 1), - datum: makeDatum(domainObject, series, count - 1) + datum: self.makeDatum(domainObject, series, count - 1) }); } } @@ -188,6 +169,11 @@ define( function cacheObjectReferences(objects) { self.telemetryObjects = objects; self.metadatas = objects.map(lookupMetadata); + + self.metadataById = {}; + objects.forEach(function (obj, i) { + self.metadataById[obj.getId()] = self.metadatas[i]; + }); // Fire callback, as this will be the first time that // telemetry objects are available, or these objects // will have changed. @@ -239,12 +225,36 @@ define( initialize(); this.unlistenToMutation = addMutationListener(); - - // Expose makeDatum for TelemetryHandler to use, but not - // as part of public API - this.makeDatum = makeDatum; } + + /** + * From a telemetry series, retrieve a single data point + * containing all fields for domains/ranges + * @private + */ + TelemetrySubscription.prototype.makeDatum = function (domainObject, series, index) { + var id = domainObject && domainObject.getId(), + metadata = (id && this.metadataById[id]) || {}, + result = {}; + + (metadata.domains || []).forEach(function (domain) { + result[domain.key] = + series.getDomainValue(index, domain.key); + }); + + (metadata.ranges || []).forEach(function (range) { + result[range.key] = + series.getRangeValue(index, range.key); + }); + + return result; + }; + + /** + * Terminate all underlying subscriptions. + * @private + */ TelemetrySubscription.prototype.unsubscribeAll = function () { var $q = this.$q; return this.unsubscribePromise.then(function (unsubscribes) { From 2758250833c81207c76630e5d0d998ad8354f0f2 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 11:27:50 -0700 Subject: [PATCH 45/78] [Time Conductor] Fix JSDoc Fix copy-paste error. WTD-1515 --- platform/features/conductor/src/TimeConductor.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/platform/features/conductor/src/TimeConductor.js b/platform/features/conductor/src/TimeConductor.js index ec4200ed6e..fcf8dbae04 100644 --- a/platform/features/conductor/src/TimeConductor.js +++ b/platform/features/conductor/src/TimeConductor.js @@ -33,21 +33,12 @@ define( 'use strict'; /** - * Wrapper for the `telemetry` capability which adds start/end - * times to all requests based on the current state of a time - * conductor. - * - * Note that both start and end times are in units which may - * vary depending on the domains of telemetry being used. Most - * commonly, these are UNIX timestamps in milliseconds. + * Tracks the current state of the time conductor. * * @memberof platform/features/conductor * @constructor - * @augments {platform/telemetry.TelemetryCapability} - * @param {platform/features/conductor.TimeConductor} timeConductor - * the time conductor which controls these queries - * @param {platform/telemetry.TelemetryCapability} telemetryCapability - * the wrapped capability + * @param {number} start the initial start time + * @param {number} end the initial end time */ function TimeConductor(start, end) { this.inner = { start: start, end: end }; From 86bb89a162190ac0191ce20caa011a2200017f3d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 11:31:40 -0700 Subject: [PATCH 46/78] [Time Controller] Update spec Update spec for ConductorRepresenter to reflect changes to model properties expected by TimeRangeController. WTD-1515 --- .../conductor/test/ConductorRepresenterSpec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platform/features/conductor/test/ConductorRepresenterSpec.js b/platform/features/conductor/test/ConductorRepresenterSpec.js index 931eec506f..2b1003f3c7 100644 --- a/platform/features/conductor/test/ConductorRepresenterSpec.js +++ b/platform/features/conductor/test/ConductorRepresenterSpec.js @@ -134,31 +134,31 @@ define( representer.represent(testViews[0], {}); expect(mockNewScope.conductor).toEqual({ - inner: [ 1977, 1984 ], - outer: [ 42, 12321 ] + inner: { start: 1977, end: 1984 }, + outer: { start: 42, end: 12321 } }); }); it("updates conductor state from scope", function () { var testState = { - inner: [ 42, 1984 ], - outer: [ -1977, 12321 ] + inner: { start: 42, end: 1984 }, + outer: { start: -1977, end: 12321 } }; representer.represent(testViews[0], {}); mockNewScope.conductor = testState; - fireWatch(mockNewScope, 'conductor.inner[0]', testState.inner[0]); + fireWatch(mockNewScope, 'conductor.inner.start', testState.inner.start); expect(mockConductor.displayStart).toHaveBeenCalledWith(42); - fireWatch(mockNewScope, 'conductor.inner[1]', testState.inner[1]); + fireWatch(mockNewScope, 'conductor.inner.end', testState.inner.end); expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984); - fireWatch(mockNewScope, 'conductor.outer[0]', -1977); + fireWatch(mockNewScope, 'conductor.outer.start', testState.outer.start); expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977); - fireWatch(mockNewScope, 'conductor.outer[1]', 12321); + fireWatch(mockNewScope, 'conductor.outer.end', testState.outer.end); expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321); }); From 8d209f4d19fa8409656a7ef4d698f2b745f639fa Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 11:34:31 -0700 Subject: [PATCH 47/78] [Time Controller] Fix middle-drag bug Fix bug in middle-drag introduced by refactoring, WTD-1515. --- .../general/src/controllers/TimeRangeController.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index 554cac1cad..5ac03ca796 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -124,10 +124,10 @@ define( } function startMiddleDrag() { - initialDragValue = [ - $scope.ngModel.inner.start, - $scope.ngModel.inner.end - ]; + initialDragValue = { + start: $scope.ngModel.inner.start, + end: $scope.ngModel.inner.end + }; } function toMillis(pixels) { From 2229e868ce5dad9574d7568d697d1b88c7d1f413 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 13:24:50 -0700 Subject: [PATCH 48/78] [Time Conductor] Fix logic around end times Fix logic for updating end times after refactoring to clarify variable/property naming, WTD-1515 --- .../commonUI/general/src/controllers/TimeRangeController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index 5ac03ca796..f603ec2ccc 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -186,7 +186,7 @@ define( ngModel.inner.start = Math.max(ngModel.outer.start, ngModel.inner.start); ngModel.inner.end = - Math.max(ngModel.outer.end, ngModel.inner.end); + Math.max(ngModel.outer.start, ngModel.inner.end); updateViewForInnerSpanFromModel(ngModel); } From 62962e119ef254187cb0f22a66cc9de4913cdae6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 10 Sep 2015 15:18:24 -0700 Subject: [PATCH 49/78] [Time Controller] Decorate telemetry service Decorate telemetry service instead of capability service to enforce time conductor bounds. WTD-1515. --- platform/features/conductor/bundle.json | 4 +- .../src/ConductorTelemetryCapability.js | 81 -------------- ...ator.js => ConductorTelemetryDecorator.js} | 50 +++++---- .../test/ConductorCapabilityDecoratorSpec.js | 101 ----------------- .../test/ConductorTelemetryCapabilitySpec.js | 105 ------------------ platform/features/conductor/test/suite.json | 2 - 6 files changed, 31 insertions(+), 312 deletions(-) delete mode 100644 platform/features/conductor/src/ConductorTelemetryCapability.js rename platform/features/conductor/src/{ConductorCapabilityDecorator.js => ConductorTelemetryDecorator.js} (54%) delete mode 100644 platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js delete mode 100644 platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js diff --git a/platform/features/conductor/bundle.json b/platform/features/conductor/bundle.json index 39d45628bb..b230f9d370 100644 --- a/platform/features/conductor/bundle.json +++ b/platform/features/conductor/bundle.json @@ -9,8 +9,8 @@ "components": [ { "type": "decorator", - "provides": "capabilityService", - "implementation": "ConductorCapabilityDecorator.js", + "provides": "telemetryService", + "implementation": "ConductorTelemetryDecorator.js", "depends": [ "conductorService" ] } ], diff --git a/platform/features/conductor/src/ConductorTelemetryCapability.js b/platform/features/conductor/src/ConductorTelemetryCapability.js deleted file mode 100644 index ca9a18a24b..0000000000 --- a/platform/features/conductor/src/ConductorTelemetryCapability.js +++ /dev/null @@ -1,81 +0,0 @@ -/***************************************************************************** - * 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'; - - /** - * Wrapper for the `telemetry` capability which adds start/end - * times to all requests based on the current state of a time - * conductor. - * - * @constructor - * @memberof platform/features/conductor - * @augments {platform/telemetry.TelemetryCapability} - * @param {platform/features/conductor.TimeConductor} timeConductor - * the time conductor which controls these queries - * @param {platform/telemetry.TelemetryCapability} telemetryCapability - * the wrapped capability - */ - function ConductorTelemetryCapability(timeConductor, telemetryCapability) { - this.timeConductor = timeConductor; - this.wrappedCapability = telemetryCapability; - } - - ConductorTelemetryCapability.prototype.amendRequest = function (request) { - request = request || {}; - - // This isn't really the right check, but it happens to distinguish - // plots (which want to query for the full set of data for easy - // panning) from views like fixed position, which only want the - // single latest data point. - if (request.size !== undefined) { - request.start = this.timeConductor.displayStart(); - request.end = this.timeConductor.displayEnd(); - } else { - request.start = this.timeConductor.queryStart(); - request.end = this.timeConductor.queryEnd(); - } - - return request; - }; - - ConductorTelemetryCapability.prototype.getMetadata = function () { - return this.wrappedCapability.getMetadata(); - }; - - ConductorTelemetryCapability.prototype.requestData = function (request) { - request = this.amendRequest(request); - return this.wrappedCapability.requestData(request); - }; - - ConductorTelemetryCapability.prototype.subscribe = function (callback, request) { - request = this.amendRequest(request); - return this.wrappedCapability.subscribe(callback, request); - }; - - return ConductorTelemetryCapability; - } -); diff --git a/platform/features/conductor/src/ConductorCapabilityDecorator.js b/platform/features/conductor/src/ConductorTelemetryDecorator.js similarity index 54% rename from platform/features/conductor/src/ConductorCapabilityDecorator.js rename to platform/features/conductor/src/ConductorTelemetryDecorator.js index 7c6ba46133..6d75125b7a 100644 --- a/platform/features/conductor/src/ConductorCapabilityDecorator.js +++ b/platform/features/conductor/src/ConductorTelemetryDecorator.js @@ -22,43 +22,51 @@ /*global define*/ define( - ['./ConductorTelemetryCapability'], - function (ConductorTelemetryCapability) { + [], + function () { 'use strict'; /** - * Decorates the `capabilityService` such that any exposed `telemetry` - * capabilities have their requests mediated by the time conductor. + * Decorates the `telemetryService` such that requests are + * mediated by the time conductor. * * @constructor * @memberof platform/features/conductor - * @implements {CapabilityService} + * @implements {TelemetryService} * @param {platform/features/conductor.ConductorService} conductorServe * the service which exposes the global time conductor - * @param {CapabilityService} capabilityService the decorated service + * @param {TelemetryService} telemetryService the decorated service */ - function ConductorCapabilityDecorator(conductorService, capabilityService) { + function ConductorTelemetryDecorator(conductorService, telemetryService) { this.conductorService = conductorService; - this.capabilityService = capabilityService; + this.telemetryService = telemetryService; } - ConductorCapabilityDecorator.prototype.getCapabilities = function (model) { - var capabilities = this.capabilityService.getCapabilities(model), - TelemetryCapability = capabilities.telemetry, - conductorService = this.conductorService; + ConductorTelemetryDecorator.prototype.amendRequests = function (requests) { + var conductor = this.conductorService.getConductor(), + start = conductor.displayStart(), + end = conductor.displayEnd(); - if (TelemetryCapability) { - capabilities.telemetry = function (domainObject) { - return new ConductorTelemetryCapability( - conductorService.getConductor(), - new TelemetryCapability(domainObject) - ); - }; + function amendRequest(request) { + request = request || {}; + request.start = start; + request.end = end; + return request; } - return capabilities; + return (requests || []).map(amendRequest); }; - return ConductorCapabilityDecorator; + ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) { + return this.telemetryService + .requestTelemetry(this.amendRequests(requests)); + }; + + ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) { + return this.telemetryService + .subscribe(callback, this.amendRequests(requests)); + }; + + return ConductorTelemetryDecorator; } ); diff --git a/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js b/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js deleted file mode 100644 index b98270148b..0000000000 --- a/platform/features/conductor/test/ConductorCapabilityDecoratorSpec.js +++ /dev/null @@ -1,101 +0,0 @@ -/***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT Web is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT Web includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ - -/** - * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. - */ -define( - ["../src/ConductorCapabilityDecorator"], - function (ConductorCapabilityDecorator) { - "use strict"; - - describe("ConductorCapabilityDecorator", function () { - var mockCapabilityService, - mockConductorService, - testModel, - testCapabilities, - decorator; - - function instantiate(Constructor) { - return new Constructor(); - } - - beforeEach(function () { - testCapabilities = { - telemetry: jasmine.createSpy('Telemetry'), - other: jasmine.createSpy('Other') - }; - - mockCapabilityService = jasmine.createSpyObj( - 'capabilityService', - [ 'getCapabilities' ] - ); - mockConductorService = jasmine.createSpyObj( - 'conductorService', - [ 'getConductor' ] - ); - testModel = { someKey: "some value" }; - - mockCapabilityService.getCapabilities.andCallFake(function () { - // Wrap with object.create so we can still - // reliably expect properties of testCapabilities itself - return Object.create(testCapabilities); - }); - - decorator = new ConductorCapabilityDecorator( - mockConductorService, - mockCapabilityService - ); - }); - - it("delegates to the decorated capability service", function () { - expect(mockCapabilityService.getCapabilities).not.toHaveBeenCalled(); - decorator.getCapabilities(testModel); - expect(mockCapabilityService.getCapabilities).toHaveBeenCalled(); - }); - - it("wraps the 'telemetry' capability of objects", function () { - var capabilities = decorator.getCapabilities(testModel); - expect(capabilities.telemetry) - .not.toBe(testCapabilities.telemetry); - - // Should wrap - verify by invocation - expect(testCapabilities.telemetry).not.toHaveBeenCalled(); - instantiate(capabilities.telemetry); - expect(testCapabilities.telemetry).toHaveBeenCalled(); - }); - - it("does not wrap other capabilities", function () { - var capabilities = decorator.getCapabilities(testModel); - expect(capabilities.other) - .toBe(testCapabilities.other); - }); - - it("gets a time conductor from the conductorService", function () { - expect(mockConductorService.getConductor).not.toHaveBeenCalled(); - instantiate(decorator.getCapabilities(testModel).telemetry); - expect(mockConductorService.getConductor).toHaveBeenCalled(); - }); - }); - } -); diff --git a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js b/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js deleted file mode 100644 index 2cc30229bc..0000000000 --- a/platform/features/conductor/test/ConductorTelemetryCapabilitySpec.js +++ /dev/null @@ -1,105 +0,0 @@ -/***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT Web is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT Web includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ - -/** - * EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015. - */ -define( - ["../src/ConductorTelemetryCapability"], - function (ConductorTelemetryCapability) { - "use strict"; - - describe("ConductorTelemetryCapability", function () { - var mockConductor, - mockTelemetryCapability, - mockUnsubscribe, - testMetadata, - testStartTime, - testEndTime, - conductorTelemetryCapability; - - beforeEach(function () { - mockConductor = jasmine.createSpyObj( - 'timeConductor', - [ - 'queryStart', - 'queryEnd', - 'displayStart', - 'displayEnd' - ] - ); - mockTelemetryCapability = jasmine.createSpyObj( - 'telemetry', - [ 'getMetadata', 'requestData', 'subscribe' ] - ); - mockUnsubscribe = jasmine.createSpy('unsubscribe'); - - testStartTime = 42; - testEndTime = 12321; - testMetadata = { someKey: 'some value' }; - mockTelemetryCapability.getMetadata.andReturn(testMetadata); - mockTelemetryCapability.subscribe.andReturn(mockUnsubscribe); - mockConductor.queryStart.andReturn(testStartTime); - mockConductor.queryEnd.andReturn(testEndTime); - - conductorTelemetryCapability = new ConductorTelemetryCapability( - mockConductor, - mockTelemetryCapability - ); - }); - - it("simply delegates getMetadata calls", function () { - expect(conductorTelemetryCapability.getMetadata()) - .toBe(testMetadata); - }); - - it("adds start/end times to requests", function () { - conductorTelemetryCapability - .requestData({ someKey: "some value" }); - expect(mockTelemetryCapability.requestData).toHaveBeenCalledWith({ - someKey: "some value", - start: testStartTime, - end: testEndTime - }); - }); - - it("adds start/end times to subscribe calls", function () { - var mockCallback = jasmine.createSpy('callback'), - testRequest = { someKey: "some value" }; - expect(conductorTelemetryCapability.subscribe( - mockCallback, - testRequest - )).toBe(mockUnsubscribe); - expect(mockTelemetryCapability.subscribe).toHaveBeenCalledWith( - mockCallback, - { - someKey: "some value", - start: testStartTime, - end: testEndTime - } - ); - }); - - }); - } -); diff --git a/platform/features/conductor/test/suite.json b/platform/features/conductor/test/suite.json index 85a155c5df..d4d291e4d7 100644 --- a/platform/features/conductor/test/suite.json +++ b/platform/features/conductor/test/suite.json @@ -1,7 +1,5 @@ [ - "ConductorCapabilityDecorator", "ConductorRepresenter", "ConductorService", - "ConductorTelemetryCapability", "TimeConductor" ] From 2a14cf2dfcde1cdb9a5eb3437e0b0a419fd6a929 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 11 Sep 2015 11:31:12 -0700 Subject: [PATCH 50/78] [Time Controller] Only listen for display-bounds changes ...from plot. WTD-1515 --- platform/features/plot/src/PlotController.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 3b846d33b8..47cf1f31c6 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -66,6 +66,7 @@ define( cachedObjects = [], updater, lastBounds, + throttledRequery, handle; // Populate the scope with axis information (specifically, options @@ -96,7 +97,7 @@ define( } // Change the displayable bounds - function setBasePanZoom(unused, bounds) { + function setBasePanZoom(bounds) { var start = bounds.start, end = bounds.end; if (updater) { @@ -121,7 +122,7 @@ define( ); // Keep any externally-provided bounds if (lastBounds) { - setBasePanZoom({}, lastBounds); + setBasePanZoom(lastBounds); } } @@ -181,14 +182,17 @@ define( } } - // Initiate a new query for data because query bounds changed - function requery() { + // Respond to a display bounds change (requery for data) + function changeDisplayBounds(event, bounds) { + setBasePanZoom(bounds); if (handle) { recreateUpdater(); - requestTelemetry(); + throttledRequery(); } } + throttledRequery = throttle(requestTelemetry, 250); + this.modeOptions = new PlotModeOptions([], subPlotFactory); this.updateValues = updateValues; @@ -202,10 +206,7 @@ define( $scope.$watch('domainObject', subscribe); // Respond to external bounds changes - $scope.$on("telemetry:display:bounds", setBasePanZoom); - - // Respond to external query range changes - $scope.$on("telemetry:query:bounds", throttle(requery, 250)); + $scope.$on("telemetry:display:bounds", changeDisplayBounds); // Unsubscribe when the plot is destroyed $scope.$on("$destroy", releaseSubscription); From 890aafc20358dfa2a5a16cc3ad8df1c9875f405c Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 10:02:59 -0700 Subject: [PATCH 51/78] [Time Controller] Filter out realtime updates Filter out realtime updates that are outside of the time controller's range. WTD-1515 --- .../src/ConductorTelemetryDecorator.js | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/platform/features/conductor/src/ConductorTelemetryDecorator.js b/platform/features/conductor/src/ConductorTelemetryDecorator.js index 6d75125b7a..7687715f30 100644 --- a/platform/features/conductor/src/ConductorTelemetryDecorator.js +++ b/platform/features/conductor/src/ConductorTelemetryDecorator.js @@ -42,6 +42,34 @@ define( this.telemetryService = telemetryService; } + // Strip out any realtime data series that is outside of the conductor's + // bounds. + ConductorTelemetryDecorator.prototype.stripRealtime = function (packaged) { + var conductor = this.conductorService.getConductor(), + start = conductor.displayStart(), + end = conductor.displayEnd(), + repackaged = {}; + + function filterSource(packagedBySource) { + var repackagedBySource = {}; + + Object.keys(packagedBySource).filter(function (k) { + return packagedBySource[k].getPointCount() > 0 && + packagedBySource[k].getDomainValue(0) <= end; + }).forEach(function (k) { + repackagedBySource[k] = packagedBySource[k]; + }); + + return repackagedBySource; + } + + Object.keys(packaged).forEach(function (source) { + repackaged[source] = filterSource(packaged[source]); + }); + + return repackaged; + }; + ConductorTelemetryDecorator.prototype.amendRequests = function (requests) { var conductor = this.conductorService.getConductor(), start = conductor.displayStart(), @@ -63,8 +91,14 @@ define( }; ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) { + var self = this; + + function internalCallback(packagedSeries) { + return callback(self.stripRealtime(packagedSeries)); + } + return this.telemetryService - .subscribe(callback, this.amendRequests(requests)); + .subscribe(internalCallback, this.amendRequests(requests)); }; return ConductorTelemetryDecorator; From f42c5ca1e525eb3f956042fc62e068630b350767 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 11:25:42 -0700 Subject: [PATCH 52/78] [Time Conductor] Subset to display bounds WTD-1515 --- .../src/ConductorTelemetryDecorator.js | 26 ++++++----- .../conductor/src/ConductorTelemetrySeries.js | 44 +++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 platform/features/conductor/src/ConductorTelemetrySeries.js diff --git a/platform/features/conductor/src/ConductorTelemetryDecorator.js b/platform/features/conductor/src/ConductorTelemetryDecorator.js index 7687715f30..9c8126c33f 100644 --- a/platform/features/conductor/src/ConductorTelemetryDecorator.js +++ b/platform/features/conductor/src/ConductorTelemetryDecorator.js @@ -22,8 +22,8 @@ /*global define*/ define( - [], - function () { + ['./ConductorTelemetrySeries'], + function (ConductorTelemetrySeries) { 'use strict'; /** @@ -44,20 +44,18 @@ define( // Strip out any realtime data series that is outside of the conductor's // bounds. - ConductorTelemetryDecorator.prototype.stripRealtime = function (packaged) { + ConductorTelemetryDecorator.prototype.pruneNonDisplayable = function (packaged) { var conductor = this.conductorService.getConductor(), - start = conductor.displayStart(), - end = conductor.displayEnd(), repackaged = {}; function filterSource(packagedBySource) { var repackagedBySource = {}; - Object.keys(packagedBySource).filter(function (k) { - return packagedBySource[k].getPointCount() > 0 && - packagedBySource[k].getDomainValue(0) <= end; - }).forEach(function (k) { - repackagedBySource[k] = packagedBySource[k]; + Object.keys(packagedBySource).forEach(function (k) { + repackagedBySource[k] = new ConductorTelemetrySeries( + packagedBySource[k], + conductor + ); }); return repackagedBySource; @@ -86,15 +84,19 @@ define( }; ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) { + var self = this; return this.telemetryService - .requestTelemetry(this.amendRequests(requests)); + .requestTelemetry(this.amendRequests(requests)) + .then(function (packaged) { + return self.pruneNonDisplayable(packaged); + }); }; ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) { var self = this; function internalCallback(packagedSeries) { - return callback(self.stripRealtime(packagedSeries)); + return callback(self.pruneNonDisplayable(packagedSeries)); } return this.telemetryService diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js new file mode 100644 index 0000000000..044e35ca72 --- /dev/null +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -0,0 +1,44 @@ +/*global define*/ + +define( + function () { + 'use strict'; + + function ConductorTelemetrySeries(series, conductor) { + var max = series.getPointCount() - 1; + + function binSearch(min, max, value) { + var mid = Math.floor((min + max) / 2), + domainValue = series.getDomainValue(mid); + + if (min >= max) { + return min; + } + + if (domainValue < value) { + return binSearch(mid + 1, max); + } else { + return binSearch(min, mid - 1); + } + } + + this.startIndex = binSearch(0, max, conductor.displayStart()); + this.endIndex = binSearch(0, max, conductor.displayEnd()); + this.series = series; + } + + ConductorTelemetrySeries.prototype.getPointCount = function () { + return Math.max(0, this.endIndex - this.startIndex); + }; + + ConductorTelemetrySeries.prototype.getDomainValue = function (i, d) { + return this.series.getDomainValue(i + this.startIndex, d); + }; + + ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) { + return this.series.getDomainValue(i + this.startIndex, r); + }; + + return ConductorTelemetrySeries; + } +); From 24449d2dccbf845543fb7ad9544868f2d68b1934 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 11:44:50 -0700 Subject: [PATCH 53/78] [Time Controller] Fix series subsetting Fix binary search implementation used to subset telemetry series for time conductor. WTD-1515 --- .../conductor/src/ConductorTelemetrySeries.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js index 044e35ca72..94daeb067a 100644 --- a/platform/features/conductor/src/ConductorTelemetrySeries.js +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -11,15 +11,9 @@ define( var mid = Math.floor((min + max) / 2), domainValue = series.getDomainValue(mid); - if (min >= max) { - return min; - } - - if (domainValue < value) { - return binSearch(mid + 1, max); - } else { - return binSearch(min, mid - 1); - } + return min >= max ? min : + domainValue < value ? binSearch(mid + 1, max, value) : + binSearch(min, mid - 1, value); } this.startIndex = binSearch(0, max, conductor.displayStart()); From de99969f0af5be8db408fd396addff723fb4feda Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 14:39:17 -0700 Subject: [PATCH 54/78] [Time Controller] Return range values Delegate retrieval of range values appropriately in conductor-driven telemetry series subset. WTD-1515 --- platform/features/conductor/src/ConductorTelemetrySeries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js index 94daeb067a..1925bc1c70 100644 --- a/platform/features/conductor/src/ConductorTelemetrySeries.js +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -30,7 +30,7 @@ define( }; ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) { - return this.series.getDomainValue(i + this.startIndex, r); + return this.series.getRangeValue(i + this.startIndex, r); }; return ConductorTelemetrySeries; From 53369ec0dcdde29dbb968308584bd1a2e3abfd5f Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 16:36:41 -0700 Subject: [PATCH 55/78] [Time Conductor] Avoid searching outside of series Don't look up domain values while subsetting a telemetry series until after checking to ensure that there is some segment of the series left to search. WTD-1515 --- .../features/conductor/src/ConductorTelemetrySeries.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js index 1925bc1c70..a072ac34e4 100644 --- a/platform/features/conductor/src/ConductorTelemetrySeries.js +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -8,12 +8,12 @@ define( var max = series.getPointCount() - 1; function binSearch(min, max, value) { - var mid = Math.floor((min + max) / 2), - domainValue = series.getDomainValue(mid); + var mid = Math.floor((min + max) / 2); return min >= max ? min : - domainValue < value ? binSearch(mid + 1, max, value) : - binSearch(min, mid - 1, value); + series.getDomainValue(mid) < value ? + binSearch(mid + 1, max, value) : + binSearch(min, mid - 1, value); } this.startIndex = binSearch(0, max, conductor.displayStart()); From d0478c3433d8a8a3a67a38434f1f172b72ef5476 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Mon, 14 Sep 2015 16:37:29 -0700 Subject: [PATCH 56/78] [Scrolling List] Check for existence of limit Check for existence of limit capability while evaluating limits in a scrolling list view. WTD-1515 --- platform/features/scrolling/src/RangeColumn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/scrolling/src/RangeColumn.js b/platform/features/scrolling/src/RangeColumn.js index 637a68517d..0b76fbfda0 100644 --- a/platform/features/scrolling/src/RangeColumn.js +++ b/platform/features/scrolling/src/RangeColumn.js @@ -55,7 +55,7 @@ define( var range = this.rangeMetadata.key, limit = domainObject.getCapability('limit'), value = datum[range], - alarm = limit.evaluate(datum, range); + alarm = limit && limit.evaluate(datum, range); return { cssClass: alarm && alarm.cssClass, From cd98886a43073c1bc4f14d06e0b256687b80c875 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 08:59:57 -0700 Subject: [PATCH 57/78] [Time Conductor] Add license header WTD-1515 --- .../templates/controls/time-controller.html | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 6b8f3e4fc8..34b87d24ea 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -1,25 +1,23 @@
From d0b5bb2d21281f913316a2e91581e553687dd8e8 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 10:00:41 -0700 Subject: [PATCH 58/78] [Time Conductor] Begin using date-time controls WTD-1515 --- .../templates/controls/time-controller.html | 8 +-- .../src/controllers/TimeRangeController.js | 17 ++--- .../controllers/TimeRangeControllerSpec.js | 67 +++++++++++++++++++ platform/commonUI/general/test/suite.json | 3 +- 4 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 34b87d24ea..3fb99b1862 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -22,12 +22,8 @@
- Start: - End: + Start: + End:
diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index f603ec2ccc..3e792af052 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -40,11 +40,6 @@ define( return moment.utc(ts).format(DATE_FORMAT); } - function parseTimestamp(text, fallback) { - var m = moment.utc(text, DATE_FORMAT); - return m.isValid() ? m.valueOf() : fallback; - } - // From 0.0-1.0 to "0%"-"1%" function toPercent(p) { return (100 * p) + "%"; @@ -103,8 +98,8 @@ define( ngModel.inner = ngModel.inner || copyBounds(ngModel.outer); // First, dates for the date pickers for outer bounds - $scope.startOuterDate = formatTimestamp(ngModel.outer.start); - $scope.endOuterDate = formatTimestamp(ngModel.outer.end); + $scope.startOuterDate = new Date(ngModel.outer.start); + $scope.endOuterDate = new Date(ngModel.outer.end); // Then various updates for the inner span updateViewForInnerSpanFromModel(ngModel); @@ -177,10 +172,10 @@ define( updateViewFromModel($scope.ngModel); } - function updateOuterStart(text) { + function updateOuterStart(date) { var ngModel = $scope.ngModel; ngModel.outer.start = - parseTimestamp(text, ngModel.outer.start); + date.getTime(); ngModel.outer.end = Math.max(ngModel.outer.start, ngModel.outer.end); ngModel.inner.start = @@ -190,10 +185,10 @@ define( updateViewForInnerSpanFromModel(ngModel); } - function updateOuterEnd(text) { + function updateOuterEnd(date) { var ngModel = $scope.ngModel; ngModel.outer.end = - parseTimestamp(text, ngModel.outer.end); + date.getTime(); ngModel.outer.start = Math.min(ngModel.outer.end, ngModel.outer.start); ngModel.inner.start = diff --git a/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js b/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js new file mode 100644 index 0000000000..48411756b0 --- /dev/null +++ b/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * 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/TimeRangeController"], + function (TimeRangeController) { + "use strict"; + + describe("The TimeRangeController", function () { + var mockScope, + mockNow, + controller; + + function fireWatch(expr, value) { + mockScope.$watch.calls.forEach(function (call) { + if (call.args[0] === expr) { + call.args[1](value); + } + }); + } + + function fireWatchCollection(expr, value) { + mockScope.$watchCollection.calls.forEach(function (call) { + if (call.args[0] === expr) { + call.args[1](value); + } + }); + } + + beforeEach(function () { + mockScope = jasmine.createSpyObj( + "$scope", + [ "$apply", "$watch", "$watchCollection" ] + ); + mockNow = jasmine.createSpy('now'); + controller = new TimeRangeController(mockScope, mockNow); + }); + + it("watches the model that was passed in", function () { + expect(mockScope.$watchCollection) + .toHaveBeenCalledWith("ngModel", jasmine.any(Function)); + }); + + + }); + } +); diff --git a/platform/commonUI/general/test/suite.json b/platform/commonUI/general/test/suite.json index 37fc4c4b78..38f8a447ee 100644 --- a/platform/commonUI/general/test/suite.json +++ b/platform/commonUI/general/test/suite.json @@ -6,6 +6,7 @@ "controllers/GetterSetterController", "controllers/SelectorController", "controllers/SplitPaneController", + "controllers/TimeRangeController", "controllers/ToggleController", "controllers/TreeNodeController", "controllers/ViewSwitcherController", @@ -15,4 +16,4 @@ "directives/MCTScroll", "services/UrlService", "StyleSheetLoader" -] \ No newline at end of file +] From f29951140fcb6b84c3827e558fb3502fe0a0e9e3 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 10:22:43 -0700 Subject: [PATCH 59/78] [Time Conductor] Add license header, JSDoc WTD-1515 --- .../conductor/src/ConductorTelemetrySeries.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js index a072ac34e4..55fe120837 100644 --- a/platform/features/conductor/src/ConductorTelemetrySeries.js +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -1,9 +1,42 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + /*global define*/ define( function () { 'use strict'; + + /** + * Bound a series of telemetry such that it only includes + * points from within the time conductor's displayable window. + * + * @param {TelemetrySeries} series the telemetry series + * @param {platform/features/conductor.TimeConductor} the + * time conductor instance which bounds this series + * @constructor + * @implements {TelemetrySeries} + */ function ConductorTelemetrySeries(series, conductor) { var max = series.getPointCount() - 1; From 493c63be4493cc72d0b5291c0ab478bd2150d9d3 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 10:58:10 -0700 Subject: [PATCH 60/78] [Time Conductor] Test series wrapping WTD-1515 --- .../conductor/src/ConductorTelemetrySeries.js | 2 +- .../test/ConductorTelemetryDecoratorSpec.js | 35 ++++++++ .../test/ConductorTelemetrySeriesSpec.js | 86 +++++++++++++++++++ platform/features/conductor/test/suite.json | 2 + 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js create mode 100644 platform/features/conductor/test/ConductorTelemetrySeriesSpec.js diff --git a/platform/features/conductor/src/ConductorTelemetrySeries.js b/platform/features/conductor/src/ConductorTelemetrySeries.js index 55fe120837..aa6ec0ec63 100644 --- a/platform/features/conductor/src/ConductorTelemetrySeries.js +++ b/platform/features/conductor/src/ConductorTelemetrySeries.js @@ -43,7 +43,7 @@ define( function binSearch(min, max, value) { var mid = Math.floor((min + max) / 2); - return min >= max ? min : + return min > max ? min : series.getDomainValue(mid) < value ? binSearch(mid + 1, max, value) : binSearch(min, mid - 1, value); diff --git a/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js b/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js new file mode 100644 index 0000000000..39b6436131 --- /dev/null +++ b/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js @@ -0,0 +1,35 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + + +define( + ["../src/ConductorTelemetryDecorator"], + function (ConductorTelemetryDecorator) { + "use strict"; + + describe("ConductorTelemetryDecorator", function () { + + + }); + } +); diff --git a/platform/features/conductor/test/ConductorTelemetrySeriesSpec.js b/platform/features/conductor/test/ConductorTelemetrySeriesSpec.js new file mode 100644 index 0000000000..ea884f74f3 --- /dev/null +++ b/platform/features/conductor/test/ConductorTelemetrySeriesSpec.js @@ -0,0 +1,86 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/ConductorTelemetrySeries"], + function (ConductorTelemetrySeries) { + "use strict"; + + describe("ConductorTelemetrySeries", function () { + var mockSeries, + mockConductor, + testArray, + series; + + beforeEach(function () { + testArray = [ -10, 0, 42, 1977, 12321 ]; + + mockSeries = jasmine.createSpyObj( + 'series', + [ 'getPointCount', 'getDomainValue', 'getRangeValue' ] + ); + mockConductor = jasmine.createSpyObj( + 'conductor', + [ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ] + ); + + mockSeries.getPointCount.andCallFake(function () { + return testArray.length; + }); + mockSeries.getDomainValue.andCallFake(function (i) { + return testArray[i]; + }); + mockSeries.getRangeValue.andCallFake(function (i) { + return testArray[i] * 2; + }); + + mockConductor.displayStart.andReturn(0); + mockConductor.displayEnd.andReturn(2000); + + series = new ConductorTelemetrySeries( + mockSeries, + mockConductor + ); + }); + + it("reduces the apparent size of a series", function () { + expect(series.getPointCount()).toEqual(3); + }); + + it("maps domain value indexes to the displayable range", function () { + [0, 1, 2].forEach(function (i) { + expect(series.getDomainValue(i)) + .toEqual(mockSeries.getDomainValue(i + 1)); + }); + }); + + it("maps range value indexes to the displayable range", function () { + [0, 1, 2].forEach(function (i) { + expect(series.getRangeValue(i)) + .toEqual(mockSeries.getRangeValue(i + 1)); + }); + }); + + }); + } +); diff --git a/platform/features/conductor/test/suite.json b/platform/features/conductor/test/suite.json index d4d291e4d7..0c469617de 100644 --- a/platform/features/conductor/test/suite.json +++ b/platform/features/conductor/test/suite.json @@ -1,5 +1,7 @@ [ "ConductorRepresenter", "ConductorService", + "ConductorTelemetryDecorator", + "ConductorTelemetrySeries", "TimeConductor" ] From 9ebf157ec04408b1e1bd8807199e0265c0f83541 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 11:18:28 -0700 Subject: [PATCH 61/78] [Time Conductor] Test telemetry service decorator WTD-1515 --- .../test/ConductorTelemetryDecoratorSpec.js | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js b/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js index 39b6436131..84812b541a 100644 --- a/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js +++ b/platform/features/conductor/test/ConductorTelemetryDecoratorSpec.js @@ -28,7 +28,109 @@ define( "use strict"; describe("ConductorTelemetryDecorator", function () { + var mockTelemetryService, + mockConductorService, + mockConductor, + mockPromise, + mockSeries, + decorator; + function seriesIsInWindow(series) { + var i, v, inWindow = true; + for (i = 0; i < series.getPointCount(); i += 1) { + v = series.getDomainValue(i); + inWindow = inWindow && (v >= mockConductor.displayStart()); + inWindow = inWindow && (v <= mockConductor.displayEnd()); + } + return inWindow; + } + + beforeEach(function () { + mockTelemetryService = jasmine.createSpyObj( + 'telemetryService', + [ 'requestTelemetry', 'subscribe' ] + ); + mockConductorService = jasmine.createSpyObj( + 'conductorService', + ['getConductor'] + ); + mockConductor = jasmine.createSpyObj( + 'conductor', + [ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ] + ); + mockPromise = jasmine.createSpyObj( + 'promise', + ['then'] + ); + mockSeries = jasmine.createSpyObj( + 'series', + [ 'getPointCount', 'getDomainValue', 'getRangeValue' ] + ); + + mockTelemetryService.requestTelemetry.andReturn(mockPromise); + mockConductorService.getConductor.andReturn(mockConductor); + + // Prepare test series; make sure it has a broad range of + // domain values, with at least some in the query-able range + mockSeries.getPointCount.andReturn(1000); + mockSeries.getDomainValue.andCallFake(function (i) { + var j = i - 500; + return j * j * j; + }); + + mockConductor.queryStart.andReturn(-12321); + mockConductor.queryEnd.andReturn(-12321); + mockConductor.displayStart.andReturn(42); + mockConductor.displayEnd.andReturn(1977); + + decorator = new ConductorTelemetryDecorator( + mockConductorService, + mockTelemetryService + ); + }); + + it("adds display start/end times to historical requests", function () { + decorator.requestTelemetry([{ someKey: "some value" }]); + expect(mockTelemetryService.requestTelemetry) + .toHaveBeenCalledWith([{ + someKey: "some value", + start: mockConductor.displayStart(), + end: mockConductor.displayEnd() + }]); + }); + + it("adds display start/end times to subscription requests", function () { + var mockCallback = jasmine.createSpy('callback'); + decorator.subscribe(mockCallback, [{ someKey: "some value" }]); + expect(mockTelemetryService.subscribe) + .toHaveBeenCalledWith(jasmine.any(Function), [{ + someKey: "some value", + start: mockConductor.displayStart(), + end: mockConductor.displayEnd() + }]); + }); + + it("prunes historical values to the displayable range", function () { + var packagedTelemetry; + decorator.requestTelemetry([{ source: "abc", key: "xyz" }]); + packagedTelemetry = mockPromise.then.mostRecentCall.args[0]({ + "abc": { "xyz": mockSeries } + }); + expect(seriesIsInWindow(packagedTelemetry.abc.xyz)) + .toBeTruthy(); + }); + + it("prunes subscribed values to the displayable range", function () { + var mockCallback = jasmine.createSpy('callback'), + packagedTelemetry; + decorator.subscribe(mockCallback, [{ source: "abc", key: "xyz" }]); + mockTelemetryService.subscribe.mostRecentCall.args[0]({ + "abc": { "xyz": mockSeries } + }); + packagedTelemetry = mockCallback.mostRecentCall.args[0]; + expect(seriesIsInWindow(packagedTelemetry.abc.xyz)) + .toBeTruthy(); + }); }); } From 6f28ab01452c5c9d86f321bff8254226ac7441e0 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 13:08:05 -0700 Subject: [PATCH 62/78] [Time Conductor] Begin adding custom date picker WTD-1515 --- platform/commonUI/general/bundle.json | 4 ++ .../templates/controls/datetime-picker.html | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 platform/commonUI/general/res/templates/controls/datetime-picker.html diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 220dea7f7a..12290ef1c0 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -54,6 +54,10 @@ { "key": "time-controller", "templateUrl": "templates/controls/time-controller.html" + }, + { + "key": "datetime-picker", + "templateUrl": "templates/controls/datetime-picker.html" } ], "controllers": [ diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html new file mode 100644 index 0000000000..d73eca90bf --- /dev/null +++ b/platform/commonUI/general/res/templates/controls/datetime-picker.html @@ -0,0 +1,51 @@ + + +
+
+ < + March 2016 + > +
+
+ + + + + + + +
+ {{day}} +
+
{{wk + d}}
+
{{wk * d}}
+
+
+
+
{{name}}
+ +
+
From cf76583ed7096b7ccd57f82fd5cad58464b57e12 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 14:50:05 -0700 Subject: [PATCH 63/78] [Time Conductor] Add inline styles to datetime-picker --- .../templates/controls/datetime-picker.html | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html index d73eca90bf..39486ba7d1 100644 --- a/platform/commonUI/general/res/templates/controls/datetime-picker.html +++ b/platform/commonUI/general/res/templates/controls/datetime-picker.html @@ -21,29 +21,33 @@ -->
-
- < - March 2016 - > +
+
+ < + March 2016 + > +
+
+ + + + + + + +
+ {{day}} +
+
{{wk + d}}
+
{{wk * d}}
+
+
-
- - - - - - - -
- {{day}} -
-
{{wk + d}}
-
{{wk * d}}
-
-
-
+
{{name}}
- From 797046aca4184233bbc24725b3e92ec2b8244273 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 15:24:54 -0700 Subject: [PATCH 64/78] [Time Conductor] Populate datetime picker WTD-1515 --- platform/commonUI/general/bundle.json | 5 + .../templates/controls/datetime-picker.html | 24 ++-- .../controllers/DateTimePickerController.js | 125 ++++++++++++++++++ 3 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 platform/commonUI/general/src/controllers/DateTimePickerController.js diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 12290ef1c0..1c8c24af15 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -66,6 +66,11 @@ "implementation": "controllers/TimeRangeController.js", "depends": [ "$scope", "now" ] }, + { + "key": "DateTimePickerController", + "implementation": "controllers/DateTimePickerController.js", + "depends": [ "$scope" ] + }, { "key": "TreeNodeController", "implementation": "controllers/TreeNodeController.js", diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html index 39486ba7d1..7e51d68d2a 100644 --- a/platform/commonUI/general/res/templates/controls/datetime-picker.html +++ b/platform/commonUI/general/res/templates/controls/datetime-picker.html @@ -20,12 +20,12 @@ at runtime from the About dialog for additional information. --> -
+
- < - March 2016 - > + < + {{month}} {{year}} + >
@@ -34,21 +34,23 @@ {{day}} - +
-
{{wk + d}}
-
{{wk * d}}
+ ng-repeat="cell in row" + ng-class='{ disabled: !isSelectable(cell) }'> +
{{cell.day}}
+
{{cell.dayOfYear}}
-
{{name}}
+ ng-repeat="key in ['hours', 'minutes', 'seconds']" + ng-if="parameters[key]"> +
{{nameFor(key)}}
diff --git a/platform/commonUI/general/src/controllers/DateTimePickerController.js b/platform/commonUI/general/src/controllers/DateTimePickerController.js new file mode 100644 index 0000000000..55693b95c2 --- /dev/null +++ b/platform/commonUI/general/src/controllers/DateTimePickerController.js @@ -0,0 +1,125 @@ +/***************************************************************************** + * 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( + [ 'moment' ], + function (moment) { + 'use strict'; + + var TIME_NAMES = { + 'hours': "Hour", + 'minutes': "Minute", + 'seconds': "Second" + }, + MONTHS = moment.months(); + + /** + * Controller to support the date-time picker. + * + * Adds/uses the following properties in scope: + * * `year`: Year being displayed in picker + * * `month`: Month being displayed + * * `table`: Table being displayed; array of arrays of + * * `day`: Day of month + * * `dayOfYear`: Day of year + * * `month`: Month associated with the day + * * `year`: Year associated with the day. + * * `date`: Date chosen + * * `year`: Year selected + * * `month`: Month selected (0-indexed) + * * `day`: Day of month selected + * * `time`: Chosen time (hours/minutes/seconds) + * * `hours`: Hours chosen + * * `minutes`: Minutes chosen + * * `seconds`: Seconds chosen + */ + function DateTimePickerController($scope) { + var year = 2015, + month = 8; // For picker state, not model state + + function generateTableCell() { + + } + + function generateTable() { + var m = moment.utc({ year: year, month: month }).day(0), + table = [], + row, + col; + + for (row = 0; row < 6; row += 1) { + table.push([]); + for (col = 0; col < 7; col += 1) { + table[row].push({ + year: m.year(), + month: m.month(), + day: m.date(), + dayOfYear: m.dayOfYear() + }); + m.add(1, 'days'); // Next day! + } + } + + return table; + } + + function updateScopeForMonth() { + $scope.month = MONTHS[month]; + $scope.year = year; + $scope.table = generateTable(); + console.log($scope.table); + } + + $scope.isSelectable = function (cell) { + return cell.month === month; + } + + $scope.dateEquals = function (d1, d2) { + return d1.year === d2.year && + d1.month === d2.month && + d1.day === d2.day; + }; + + $scope.changeMonth = function (delta) { + month += delta; + if (month > 11) { + month = 0; + year += 1; + } + if (month < 0) { + month = 11; + year -= 1; + } + updateScopeForMonth(); + }; + + $scope.nameFor = function (key) { + return TIME_NAMES[key]; + }; + + updateScopeForMonth(); + } + + return DateTimePickerController; + } +); From d951b794e34f5c92140fd82f26b0729a91000d14 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 15:55:13 -0700 Subject: [PATCH 65/78] [Time Conductor] Support date choice ...from date-time picker. WTD-1515 --- platform/commonUI/general/bundle.json | 2 +- .../templates/controls/datetime-picker.html | 8 +- .../controllers/DateTimePickerController.js | 95 +++++++++++++++++-- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 1c8c24af15..23d8c52088 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -69,7 +69,7 @@ { "key": "DateTimePickerController", "implementation": "controllers/DateTimePickerController.js", - "depends": [ "$scope" ] + "depends": [ "$scope", "now" ] }, { "key": "TreeNodeController", diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html index 7e51d68d2a..0f43da1ab0 100644 --- a/platform/commonUI/general/res/templates/controls/datetime-picker.html +++ b/platform/commonUI/general/res/templates/controls/datetime-picker.html @@ -37,7 +37,11 @@ + ng-click="select(cell)" + ng-class='{ + disabled: !isSelectable(cell), + test: isSelected(cell) + }'>
{{cell.day}}
{{cell.dayOfYear}}
@@ -51,7 +55,7 @@
{{nameFor(key)}}
diff --git a/platform/commonUI/general/src/controllers/DateTimePickerController.js b/platform/commonUI/general/src/controllers/DateTimePickerController.js index 55693b95c2..9282d2db3c 100644 --- a/platform/commonUI/general/src/controllers/DateTimePickerController.js +++ b/platform/commonUI/general/src/controllers/DateTimePickerController.js @@ -31,7 +31,18 @@ define( 'minutes': "Minute", 'seconds': "Second" }, - MONTHS = moment.months(); + MONTHS = moment.months(), + TIME_OPTIONS = (function makeRanges() { + var arr = []; + while (arr.length < 60) { + arr.push(arr.length); + } + return { + hours: arr.slice(0, 24), + minutes: arr, + seconds: arr + }; + }()); /** * Controller to support the date-time picker. @@ -52,14 +63,13 @@ define( * * `hours`: Hours chosen * * `minutes`: Minutes chosen * * `seconds`: Seconds chosen + * + * Months are zero-indexed, day-of-months are one-indexed. */ - function DateTimePickerController($scope) { - var year = 2015, - month = 8; // For picker state, not model state - - function generateTableCell() { - - } + function DateTimePickerController($scope, now) { + var year, + month, // For picker state, not model state + interacted = false; function generateTable() { var m = moment.utc({ year: year, month: month }).day(0), @@ -90,9 +100,63 @@ define( console.log($scope.table); } + function updateFromModel(ngModel) { + var m; + + m = moment.utc(ngModel); + + $scope.date = { + year: m.year(), + month: m.month(), + day: m.date() + }; + $scope.time = { + hours: m.hour(), + minutes: m.minute(), + seconds: m.second() + }; + + //window.alert($scope.date.day + " " + ngModel); + + // Zoom to that date in the picker, but + // only if the user hasn't interacted with it yet. + if (!interacted) { + year = m.year(); + month = m.month(); + updateScopeForMonth(); + } + } + + function updateFromView() { + var m = moment.utc({ + year: $scope.date.year, + month: $scope.date.month, + day: $scope.date.day, + hour: $scope.time.hours, + minute: $scope.time.minutes, + second: $scope.time.seconds + }); + $scope.ngModel = m.valueOf(); + } + $scope.isSelectable = function (cell) { return cell.month === month; - } + }; + + $scope.isSelected = function (cell) { + var date = $scope.date || {}; + return cell.day === date.day && + cell.month === date.month && + cell.year === date.year; + }; + + $scope.select = function (cell) { + $scope.date = $scope.date || {}; + $scope.date.month = cell.month; + $scope.date.year = cell.year; + $scope.date.day = cell.day; + updateFromView(); + }; $scope.dateEquals = function (d1, d2) { return d1.year === d2.year && @@ -110,6 +174,7 @@ define( month = 11; year -= 1; } + interacted = true; updateScopeForMonth(); }; @@ -117,7 +182,19 @@ define( return TIME_NAMES[key]; }; + $scope.optionsFor = function (key) { + return TIME_OPTIONS[key]; + }; + updateScopeForMonth(); + + // Ensure some useful default + $scope.ngModel = $scope.ngModel === undefined ? + now() : $scope.ngModel; + + $scope.$watch('ngModel', updateFromModel); + $scope.$watchCollection('date', updateFromView); + $scope.$watchCollection('time', updateFromView); } return DateTimePickerController; From 6c497f3c367529ee95c1a97aa55373f8fe07be36 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 18:09:46 -0700 Subject: [PATCH 66/78] [Time Conductor] Start adding datetime picker WTD-1515 --- platform/commonUI/general/bundle.json | 10 +++ .../templates/controls/time-controller.html | 16 +++++ .../src/directives/MCTClickElsewhere.js | 70 +++++++++++++++++++ .../general/src/directives/MCTPopup.js | 69 ++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 platform/commonUI/general/src/directives/MCTClickElsewhere.js create mode 100644 platform/commonUI/general/src/directives/MCTPopup.js diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index 23d8c52088..f9fb3a6538 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -131,11 +131,21 @@ "implementation": "directives/MCTDrag.js", "depends": [ "$document" ] }, + { + "key": "mctClickElsewhere", + "implementation": "directives/MCTClickElsewhere.js", + "depends": [ "$document" ] + }, { "key": "mctResize", "implementation": "directives/MCTResize.js", "depends": [ "$timeout" ] }, + { + "key": "mctPopup", + "implementation": "directives/MCTPopup.js", + "depends": [ "$window", "$document", "$compile", "$interval" ] + }, { "key": "mctScrollX", "implementation": "directives/MCTScroll.js", diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 3fb99b1862..f762437282 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -23,7 +23,23 @@
Start: + + + + p + + +
+ + +
+
+
End: +
diff --git a/platform/commonUI/general/src/directives/MCTClickElsewhere.js b/platform/commonUI/general/src/directives/MCTClickElsewhere.js new file mode 100644 index 0000000000..a6066fc271 --- /dev/null +++ b/platform/commonUI/general/src/directives/MCTClickElsewhere.js @@ -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*/ + +define( + [], + function () { + "use strict"; + + function MCTClickElsewhere($document) { + + // Link; install event handlers. + function link(scope, element, attrs) { + // Keep a reference to the body, to attach/detach + // mouse event handlers; mousedown and mouseup cannot + // only be attached to the element being linked, as the + // mouse may leave this element during the drag. + var body = $document.find('body'); + + function clickBody(event) { + var x = event.clientX, + y = event.clientY, + rect = element[0].getBoundingClientRect(), + xMin = rect.left, + xMax = xMin + rect.width, + yMin = rect.top, + yMax = yMin + rect.height; + + if (x < xMin || x > xMax || y < yMin || y > yMax) { + scope.$eval(attrs.mctClickElsewhere); + } + } + + body.on("mousedown", clickBody); + scope.$on("$destroy", function () { + body.off("mousedown", clickBody); + }); + } + + return { + // mct-drag only makes sense as an attribute + restrict: "A", + // Link function, to install event handlers + link: link + }; + } + + return MCTClickElsewhere; + } +); + diff --git a/platform/commonUI/general/src/directives/MCTPopup.js b/platform/commonUI/general/src/directives/MCTPopup.js new file mode 100644 index 0000000000..0e9f6f1586 --- /dev/null +++ b/platform/commonUI/general/src/directives/MCTPopup.js @@ -0,0 +1,69 @@ +/***************************************************************************** + * 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 () { + var TEMPLATE = "
"; + + function MCTPopup($window, $document, $compile, $interval) { + function link(scope, element, attrs, ctrl, transclude) { + var body = $document.find('body'), + popup = $compile(TEMPLATE)(scope), + winDim = [$window.innerWidth, $window.innerHeight], + rect = element[0].getBoundingClientRect(), + position = [ rect.left, rect.top ], + isLeft = position[0] <= (winDim[0] / 2), + isTop = position[1] <= (winDim[1] / 2); + + popup.css('position', 'absolute'); + popup.css( + isLeft ? 'left' : 'right', + (isLeft ? position[0] : (winDim[0] - position[0])) + 'px' + ); + popup.css( + isTop ? 'top' : 'bottom', + (isTop ? position[1] : (winDim[1] - position[1])) + 'px' + ); + body.append(popup); + + transclude(function (clone) { + popup.append(clone); + }); + + scope.$on('$destroy', function () { + popup.remove(); + $interval.cancel(activeInterval); + }); + } + + return { + restrict: "E", + transclude: true, + link: link, + scope: {} + } + } + + return MCTPopup; + } +); From 9a78b63065bdbed5854a3fecca6d5010be31db82 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 18:34:27 -0700 Subject: [PATCH 67/78] [Time Conductor] Try to rewrite datetime picker as control --- platform/commonUI/general/bundle.json | 8 ++++---- .../res/templates/controls/datetime-picker.html | 2 +- .../res/templates/controls/time-controller.html | 11 ++++++----- .../src/controllers/DateTimePickerController.js | 9 +++++---- .../general/src/directives/MCTClickElsewhere.js | 7 +++++++ platform/commonUI/general/src/directives/MCTPopup.js | 3 +-- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json index f9fb3a6538..2f8d036bc1 100644 --- a/platform/commonUI/general/bundle.json +++ b/platform/commonUI/general/bundle.json @@ -54,10 +54,6 @@ { "key": "time-controller", "templateUrl": "templates/controls/time-controller.html" - }, - { - "key": "datetime-picker", - "templateUrl": "templates/controls/datetime-picker.html" } ], "controllers": [ @@ -249,6 +245,10 @@ { "key": "selector", "templateUrl": "templates/controls/selector.html" + }, + { + "key": "datetime-picker", + "templateUrl": "templates/controls/datetime-picker.html" } ], "licenses": [ diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html index 0f43da1ab0..dd774588d6 100644 --- a/platform/commonUI/general/res/templates/controls/datetime-picker.html +++ b/platform/commonUI/general/res/templates/controls/datetime-picker.html @@ -51,7 +51,7 @@
+ ng-if="options[key]">
{{nameFor(key)}}
+ Start: {{ngModel.outer.start}} @@ -31,10 +31,11 @@
- - + +
diff --git a/platform/commonUI/general/src/controllers/DateTimePickerController.js b/platform/commonUI/general/src/controllers/DateTimePickerController.js index 9282d2db3c..a0821ea967 100644 --- a/platform/commonUI/general/src/controllers/DateTimePickerController.js +++ b/platform/commonUI/general/src/controllers/DateTimePickerController.js @@ -136,7 +136,7 @@ define( minute: $scope.time.minutes, second: $scope.time.seconds }); - $scope.ngModel = m.valueOf(); + $scope.ngModel[$scope.field] = m.valueOf(); } $scope.isSelectable = function (cell) { @@ -189,10 +189,11 @@ define( updateScopeForMonth(); // Ensure some useful default - $scope.ngModel = $scope.ngModel === undefined ? - now() : $scope.ngModel; + $scope.ngModel[$scope.field] = + $scope.ngModel[$scope.field] === undefined ? + now() : $scope.ngModel[$scope.field]; - $scope.$watch('ngModel', updateFromModel); + $scope.$watch('ngModel[field]', updateFromModel); $scope.$watchCollection('date', updateFromView); $scope.$watchCollection('time', updateFromView); } diff --git a/platform/commonUI/general/src/directives/MCTClickElsewhere.js b/platform/commonUI/general/src/directives/MCTClickElsewhere.js index a6066fc271..1bcdbbe6b5 100644 --- a/platform/commonUI/general/src/directives/MCTClickElsewhere.js +++ b/platform/commonUI/general/src/directives/MCTClickElsewhere.js @@ -26,6 +26,13 @@ define( function () { "use strict"; + /** + * The `mct-click-elsewhere` directive will evaluate its + * associated expression whenever a `mousedown` occurs anywhere + * outside of the element that has the `mct-click-elsewhere` + * directive attached. This is useful for dismissing popups + * and the like. + */ function MCTClickElsewhere($document) { // Link; install event handlers. diff --git a/platform/commonUI/general/src/directives/MCTPopup.js b/platform/commonUI/general/src/directives/MCTPopup.js index 0e9f6f1586..6dbf3666fc 100644 --- a/platform/commonUI/general/src/directives/MCTPopup.js +++ b/platform/commonUI/general/src/directives/MCTPopup.js @@ -25,7 +25,7 @@ define( function () { var TEMPLATE = "
"; - function MCTPopup($window, $document, $compile, $interval) { + function MCTPopup($window, $document, $compile) { function link(scope, element, attrs, ctrl, transclude) { var body = $document.find('body'), popup = $compile(TEMPLATE)(scope), @@ -52,7 +52,6 @@ define( scope.$on('$destroy', function () { popup.remove(); - $interval.cancel(activeInterval); }); } From 70d9587c9b33b2e0f021bf5969610b76856f3f45 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 18:48:00 -0700 Subject: [PATCH 68/78] [Time Conductor] Wire in datetime pickers WTD-1515 --- .../templates/controls/time-controller.html | 24 ++++++++++++++----- .../src/controllers/TimeRangeController.js | 18 +++++++------- .../general/src/directives/MCTPopup.js | 4 +++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index c0fdae3009..df7b2c8455 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -22,12 +22,9 @@
- Start: {{ngModel.outer.start}} - + Start: {{startOuterText}} - - p - + p
@@ -39,7 +36,22 @@
- End: + + End: {{endOuterText}} + + p + +
+ + +
+
+
+
diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index 3e792af052..ac111ab1a9 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -172,29 +172,31 @@ define( updateViewFromModel($scope.ngModel); } - function updateOuterStart(date) { + function updateOuterStart(t) { var ngModel = $scope.ngModel; - ngModel.outer.start = - date.getTime(); ngModel.outer.end = Math.max(ngModel.outer.start, ngModel.outer.end); ngModel.inner.start = Math.max(ngModel.outer.start, ngModel.inner.start); ngModel.inner.end = Math.max(ngModel.outer.start, ngModel.inner.end); + + $scope.startOuterText = formatTimestamp(t); + updateViewForInnerSpanFromModel(ngModel); } - function updateOuterEnd(date) { + function updateOuterEnd(t) { var ngModel = $scope.ngModel; - ngModel.outer.end = - date.getTime(); ngModel.outer.start = Math.min(ngModel.outer.end, ngModel.outer.start); ngModel.inner.start = Math.min(ngModel.outer.end, ngModel.inner.start); ngModel.inner.end = Math.min(ngModel.outer.end, ngModel.inner.end); + + $scope.endOuterText = formatTimestamp(t); + updateViewForInnerSpanFromModel(ngModel); } @@ -213,8 +215,8 @@ define( $scope.$watchCollection("ngModel", updateViewFromModel); $scope.$watch("spanWidth", updateSpanWidth); - $scope.$watch("startOuterDate", updateOuterStart); - $scope.$watch("endOuterDate", updateOuterEnd); + $scope.$watch("ngModel.outer.start", updateOuterStart); + $scope.$watch("ngModel.outer.end", updateOuterEnd); } return TimeConductorController; diff --git a/platform/commonUI/general/src/directives/MCTPopup.js b/platform/commonUI/general/src/directives/MCTPopup.js index 6dbf3666fc..eebab5b690 100644 --- a/platform/commonUI/general/src/directives/MCTPopup.js +++ b/platform/commonUI/general/src/directives/MCTPopup.js @@ -23,6 +23,8 @@ define( function () { + 'use strict'; + var TEMPLATE = "
"; function MCTPopup($window, $document, $compile) { @@ -60,7 +62,7 @@ define( transclude: true, link: link, scope: {} - } + }; } return MCTPopup; From e34fe1a2895a1cef5a5f7b20a90e3ec94b09d918 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 15 Sep 2015 18:51:44 -0700 Subject: [PATCH 69/78] [Time Conductor] Tweak position, appearance ...of datetime picker popups. WTD-1515 --- .../general/res/templates/controls/time-controller.html | 4 ++-- platform/commonUI/general/src/directives/MCTPopup.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index df7b2c8455..b223513859 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -26,7 +26,7 @@ p -
p -
Date: Wed, 16 Sep 2015 10:18:57 -0700 Subject: [PATCH 70/78] [Time Conductor] Allow arguments for throttled functions WTD-1515. Ensures that bounds passed in from the time controller get appropriately captured. --- platform/core/src/services/Throttle.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/platform/core/src/services/Throttle.js b/platform/core/src/services/Throttle.js index 3d68988d6b..eda6713dec 100644 --- a/platform/core/src/services/Throttle.js +++ b/platform/core/src/services/Throttle.js @@ -36,11 +36,16 @@ define( * * Returns a function that, when invoked, will invoke `fn` after * `delay` milliseconds, only if no other invocations are pending. - * The optional argument `apply` determines whether. + * The optional argument `apply` determines whether or not a + * digest cycle should be triggered. * * The returned function will itself return a `Promise` which will * resolve to the returned value of `fn` whenever that is invoked. * + * In cases where arguments are provided, only the most recent + * set of arguments will be passed on to the throttled function + * at the time it is executed. + * * @returns {Function} * @memberof platform/core */ @@ -56,7 +61,8 @@ define( * @memberof platform/core.Throttle# */ return function (fn, delay, apply) { - var activeTimeout; + var activeTimeout, + args = []; // Clear active timeout, so that next invocation starts // a new one. @@ -64,14 +70,21 @@ define( activeTimeout = undefined; } + // Invoke the function with the latest supplied arguments. + function invoke() { + fn.apply(null, args); + } + // Defaults delay = delay || 0; apply = apply || false; return function () { + // Store arguments from this invocation + args = Array.prototype.slice.apply(arguments, [0]); // Start a timeout if needed if (!activeTimeout) { - activeTimeout = $timeout(fn, delay, apply); + activeTimeout = $timeout(invoke, delay, apply); activeTimeout.then(clearActiveTimeout); } // Return whichever timeout is active (to get From 7a97588aa5fcbea244823a947276aef068b3c1a6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 10:30:45 -0700 Subject: [PATCH 71/78] [Time Conductor] Remove debugging statement WTD-1515 --- .../commonUI/general/src/controllers/DateTimePickerController.js | 1 - 1 file changed, 1 deletion(-) diff --git a/platform/commonUI/general/src/controllers/DateTimePickerController.js b/platform/commonUI/general/src/controllers/DateTimePickerController.js index a0821ea967..11214d9357 100644 --- a/platform/commonUI/general/src/controllers/DateTimePickerController.js +++ b/platform/commonUI/general/src/controllers/DateTimePickerController.js @@ -97,7 +97,6 @@ define( $scope.month = MONTHS[month]; $scope.year = year; $scope.table = generateTable(); - console.log($scope.table); } function updateFromModel(ngModel) { From 071368c3b9103b032154288ec58703cfaa733669 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 11:04:07 -0700 Subject: [PATCH 72/78] [Time Conductor] Fix throttle bug Fix a timing/ordering issue in throttle which allowed some throttled invocations to be ignored. WTD-1515 --- platform/core/src/services/Throttle.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/platform/core/src/services/Throttle.js b/platform/core/src/services/Throttle.js index eda6713dec..60444ad6c4 100644 --- a/platform/core/src/services/Throttle.js +++ b/platform/core/src/services/Throttle.js @@ -61,18 +61,14 @@ define( * @memberof platform/core.Throttle# */ return function (fn, delay, apply) { - var activeTimeout, + var promise, // Promise for the result of throttled function args = []; - // Clear active timeout, so that next invocation starts - // a new one. - function clearActiveTimeout() { - activeTimeout = undefined; - } - - // Invoke the function with the latest supplied arguments. function invoke() { - fn.apply(null, args); + // Clear the active timeout so a new one starts next time. + promise = undefined; + // Invoke the function with the latest supplied arguments. + return fn.apply(null, args); } // Defaults @@ -83,13 +79,10 @@ define( // Store arguments from this invocation args = Array.prototype.slice.apply(arguments, [0]); // Start a timeout if needed - if (!activeTimeout) { - activeTimeout = $timeout(invoke, delay, apply); - activeTimeout.then(clearActiveTimeout); - } + promise = promise || $timeout(invoke, delay, apply); // Return whichever timeout is active (to get // a promise for the results of fn) - return activeTimeout; + return promise; }; }; } From fcd073c0102849bc273418c05642f4618a7d215f Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 11:05:41 -0700 Subject: [PATCH 73/78] [Time Conductor] Tweak plot requery Tweak approach to requerying in plot, and track pending state so there is a visual indication that plotted data may be incomplete during panning with time conductor. WTD-1515 --- platform/features/plot/src/PlotController.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 47cf1f31c6..5555d93659 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -128,6 +128,7 @@ define( // Handle new telemetry data in this plot function updateValues() { + self.pending = false; if (handle) { setupModes(handle.getTelemetryObjects()); } @@ -143,6 +144,7 @@ define( // Display new historical data as it becomes available function addHistoricalData(domainObject, series) { + self.pending = false; updater.addHistorical(domainObject, series); self.modeOptions.getModeHandler().plotTelemetry(updater); self.update(); @@ -184,14 +186,16 @@ define( // Respond to a display bounds change (requery for data) function changeDisplayBounds(event, bounds) { + self.pending = true; + releaseSubscription(); + throttledRequery(); setBasePanZoom(bounds); - if (handle) { - recreateUpdater(); - throttledRequery(); - } } - throttledRequery = throttle(requestTelemetry, 250); + // Reestablish/reissue request for telemetry + throttledRequery = throttle(function () { + subscribe($scope.domainObject); + }, 250); this.modeOptions = new PlotModeOptions([], subPlotFactory); this.updateValues = updateValues; @@ -202,6 +206,8 @@ define( .forEach(updateSubplot); }); + self.pending = true; + // Subscribe to telemetry when a domain object becomes available $scope.$watch('domainObject', subscribe); @@ -308,7 +314,7 @@ define( PlotController.prototype.isRequestPending = function () { // Placeholder; this should reflect request state // when requesting historical telemetry - return false; + return this.pending; }; return PlotController; From ad29fb0f92e2b33cc472627c4c0069d319ea691e Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 13:38:47 -0700 Subject: [PATCH 74/78] [Time Conductor] Populate FP from historical Populate fixed position view from historical telemetry when first loaded. WTD-1515 --- platform/features/layout/src/FixedController.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index fd446bcb20..7ad856036f 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -222,6 +222,11 @@ define( domainObject, updateValues ); + // Request an initial historical telemetry value + handle.request( + { size: 1 }, // Only need a single data point + updateValueFromSeries + ); } // Handle changes in the object's composition From 190f5fd0ea64000126422cf1fd0c164a8c57d790 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 15:23:08 -0700 Subject: [PATCH 75/78] [Time Conductor] Update failing specs WTD-1515 --- platform/core/test/services/ThrottleSpec.js | 9 ++++++--- platform/features/layout/test/FixedControllerSpec.js | 3 ++- platform/features/plot/test/PlotControllerSpec.js | 7 ++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/platform/core/test/services/ThrottleSpec.js b/platform/core/test/services/ThrottleSpec.js index bcaf2af363..3b361f70bb 100644 --- a/platform/core/test/services/ThrottleSpec.js +++ b/platform/core/test/services/ThrottleSpec.js @@ -45,7 +45,9 @@ define( // Verify precondition: Not called at throttle-time expect(mockTimeout).not.toHaveBeenCalled(); expect(throttled()).toEqual(mockPromise); - expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false); + expect(mockFn).not.toHaveBeenCalled(); + expect(mockTimeout) + .toHaveBeenCalledWith(jasmine.any(Function), 0, false); }); it("schedules only one timeout at a time", function () { @@ -59,10 +61,11 @@ define( it("schedules additional invocations after resolution", function () { var throttled = throttle(mockFn); throttled(); - mockPromise.then.mostRecentCall.args[0](); // Resolve timeout + mockTimeout.mostRecentCall.args[0](); // Resolve timeout throttled(); - mockPromise.then.mostRecentCall.args[0](); + mockTimeout.mostRecentCall.args[0](); throttled(); + mockTimeout.mostRecentCall.args[0](); expect(mockTimeout.calls.length).toEqual(3); }); }); diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index 95b9842a4c..31d5e06659 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -102,7 +102,8 @@ define( 'getDomainValue', 'getTelemetryObjects', 'getRangeValue', - 'getDatum' + 'getDatum', + 'request' ] ); mockEvent = jasmine.createSpyObj( diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index e9fec60aaf..d403346bf6 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -212,7 +212,12 @@ define( }); it("indicates if a request is pending", function () { - // Placeholder; need to support requesting telemetry + mockScope.$watch.mostRecentCall.args[1](mockDomainObject); + expect(controller.isRequestPending()).toBeTruthy(); + mockHandle.request.mostRecentCall.args[1]( + mockDomainObject, + mockSeries + ); expect(controller.isRequestPending()).toBeFalsy(); }); From 8f24e014e04d96641137786af70052196b3df0ea Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 16:51:28 -0700 Subject: [PATCH 76/78] [Time Conductor] Add skeleton specs Add skeleton specs to new classes added for date-time picker in time conductor. WTD-1515 --- .../DateTimePickerControllerSpec.js | 63 +++++++++++ .../test/directives/MCTClickElsewhereSpec.js | 84 ++++++++++++++ .../general/test/directives/MCTPopupSpec.js | 105 ++++++++++++++++++ platform/commonUI/general/test/suite.json | 3 + 4 files changed, 255 insertions(+) create mode 100644 platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js create mode 100644 platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js create mode 100644 platform/commonUI/general/test/directives/MCTPopupSpec.js diff --git a/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js b/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js new file mode 100644 index 0000000000..957df1b36d --- /dev/null +++ b/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js @@ -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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../../src/controllers/DateTimePickerController"], + function (DateTimePickerController) { + "use strict"; + + describe("The DateTimePickerController", function () { + var mockScope, + mockNow, + 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", + [ "$apply", "$watch", "$watchCollection" ] + ); + mockScope.ngModel = {}; + mockScope.field = "testField"; + mockNow = jasmine.createSpy('now'); + controller = new DateTimePickerController(mockScope, mockNow); + }); + + it("watches the model that was passed in", function () { + expect(mockScope.$watch).toHaveBeenCalledWith( + "ngModel[field]", + jasmine.any(Function) + ); + }); + + + }); + } +); diff --git a/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js b/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js new file mode 100644 index 0000000000..9fa17763fe --- /dev/null +++ b/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js @@ -0,0 +1,84 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define( + ["../../src/directives/MCTClickElsewhere"], + function (MCTClickElsewhere) { + "use strict"; + + var JQLITE_METHODS = [ "on", "off", "find", "parent" ]; + + describe("The mct-click-elsewhere directive", function () { + var mockDocument, + mockScope, + mockElement, + testAttrs, + mockBody, + mockParentEl, + testRect, + mctClickElsewhere; + + function testEvent(x, y) { + return { + pageX: x, + pageY: y, + preventDefault: jasmine.createSpy("preventDefault") + }; + } + + beforeEach(function () { + mockDocument = + jasmine.createSpyObj("$document", JQLITE_METHODS); + mockScope = + jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]); + mockElement = + jasmine.createSpyObj("element", JQLITE_METHODS); + mockBody = + jasmine.createSpyObj("body", JQLITE_METHODS); + mockParentEl = + jasmine.createSpyObj("parent", ["getBoundingClientRect"]); + + testAttrs = { + mctClickElsewhere: "some Angular expression" + }; + testRect = { + left: 20, + top: 42, + width: 60, + height: 75 + }; + + mockDocument.find.andReturn(mockBody); + + mctClickElsewhere = new MCTClickElsewhere(mockDocument); + mctClickElsewhere.link(mockScope, mockElement, testAttrs); + }); + + it("is valid as an attribute", function () { + expect(mctClickElsewhere.restrict).toEqual("A"); + }); + + + }); + } +); diff --git a/platform/commonUI/general/test/directives/MCTPopupSpec.js b/platform/commonUI/general/test/directives/MCTPopupSpec.js new file mode 100644 index 0000000000..94fd3f0d0e --- /dev/null +++ b/platform/commonUI/general/test/directives/MCTPopupSpec.js @@ -0,0 +1,105 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define( + ["../../src/directives/MCTPopup"], + function (MCTPopup) { + "use strict"; + + var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ]; + + describe("The mct-popup directive", function () { + var testWindow, + mockDocument, + mockCompile, + mockScope, + mockElement, + testAttrs, + mockBody, + mockTransclude, + mockParentEl, + testRect, + mctPopup; + + function testEvent(x, y) { + return { + pageX: x, + pageY: y, + preventDefault: jasmine.createSpy("preventDefault") + }; + } + + beforeEach(function () { + testWindow = + { innerWidth: 600, innerHeight: 300 }; + mockDocument = + jasmine.createSpyObj("$document", JQLITE_METHODS); + mockCompile = + jasmine.createSpy("$compile"); + mockScope = + jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]); + mockElement = + jasmine.createSpyObj("element", JQLITE_METHODS); + mockBody = + jasmine.createSpyObj("body", JQLITE_METHODS); + mockTransclude = + jasmine.createSpy("transclude"); + mockParentEl = + jasmine.createSpyObj("parent", ["getBoundingClientRect"]); + + testAttrs = { + mctClickElsewhere: "some Angular expression" + }; + testRect = { + left: 20, + top: 42, + width: 60, + height: 75 + }; + + mockDocument.find.andReturn(mockBody); + mockCompile.andReturn(jasmine.createSpy()); + mockCompile().andCallFake(function () { + return jasmine.createSpyObj("newElement", JQLITE_METHODS); + }); + mockElement.parent.andReturn([mockParentEl]); + mockParentEl.getBoundingClientRect.andReturn(testRect); + + mctPopup = new MCTPopup(testWindow, mockDocument, mockCompile); + mctPopup.link( + mockScope, + mockElement, + testAttrs, + null, + mockTransclude + ); + }); + + it("is valid as an element", function () { + expect(mctPopup.restrict).toEqual("E"); + }); + + + }); + } +); diff --git a/platform/commonUI/general/test/suite.json b/platform/commonUI/general/test/suite.json index 38f8a447ee..1427d70f3a 100644 --- a/platform/commonUI/general/test/suite.json +++ b/platform/commonUI/general/test/suite.json @@ -3,6 +3,7 @@ "controllers/BottomBarController", "controllers/ClickAwayController", "controllers/ContextMenuController", + "controllers/DateTimePickerController", "controllers/GetterSetterController", "controllers/SelectorController", "controllers/SplitPaneController", @@ -10,8 +11,10 @@ "controllers/ToggleController", "controllers/TreeNodeController", "controllers/ViewSwitcherController", + "directives/MCTClickElsewhere", "directives/MCTContainer", "directives/MCTDrag", + "directives/MCTPopup", "directives/MCTResize", "directives/MCTScroll", "services/UrlService", From de71bde62f007a31248096f44e859fec5c5eeea2 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 17:00:56 -0700 Subject: [PATCH 77/78] [Test Conductor] Add test case for requery WTD-1515 --- .../features/plot/test/PlotControllerSpec.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index d403346bf6..dcae177920 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -45,6 +45,14 @@ define( }; } + function fireEvent(name, args) { + mockScope.$on.calls.forEach(function (call) { + if (call.args[0] === name) { + call.args[1].apply(null, args || []); + } + }); + } + beforeEach(function () { mockScope = jasmine.createSpyObj( @@ -87,6 +95,7 @@ define( mockHandle.getMetadata.andReturn([{}]); mockHandle.getDomainValue.andReturn(123); mockHandle.getRangeValue.andReturn(42); + mockScope.domainObject = mockDomainObject; controller = new PlotController( mockScope, @@ -238,10 +247,20 @@ define( // Also verify precondition expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); // Destroy the scope - mockScope.$on.mostRecentCall.args[1](); + fireEvent("$destroy"); // Should have unsubscribed expect(mockHandle.unsubscribe).toHaveBeenCalled(); }); + + it("requeries when displayable bounds change", function () { + mockScope.$watch.mostRecentCall.args[1](mockDomainObject); + expect(mockHandle.request.calls.length).toEqual(1); + fireEvent("telemetry:display:bounds", [ + {}, + { start: 10, end: 100 } + ]); + expect(mockHandle.request.calls.length).toEqual(2); + }); }); } ); From f4e53a946d0ee39d856611e27fc783df06a21551 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 16 Sep 2015 18:14:30 -0700 Subject: [PATCH 78/78] [Time Conductor] Remove from active bundles Remove time conductor from set of active bundles pending clean up of markup/CSS. --- bundles.json | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles.json b/bundles.json index 7b9929d57b..35d6f11728 100644 --- a/bundles.json +++ b/bundles.json @@ -17,7 +17,6 @@ "platform/features/plot", "platform/features/scrolling", "platform/features/events", - "platform/features/conductor", "platform/forms", "platform/identity", "platform/persistence/local",