diff --git a/bundles.json b/bundles.json index 6e28332374..e70e8455f2 100644 --- a/bundles.json +++ b/bundles.json @@ -11,6 +11,7 @@ "platform/containment", "platform/execution", "platform/telemetry", + "platform/features/imagery", "platform/features/layout", "platform/features/pages", "platform/features/plot", @@ -19,6 +20,7 @@ "platform/persistence/queue", "platform/policy", + "example/imagery", "example/persistence", "example/generator" ] diff --git a/example/imagery/bundle.json b/example/imagery/bundle.json new file mode 100644 index 0000000000..1adcf4758d --- /dev/null +++ b/example/imagery/bundle.json @@ -0,0 +1,42 @@ +{ + "name": "Imagery", + "description": "Example of a component that produces image telemetry.", + "extensions": { + "components": [ + { + "implementation": "ImageTelemetryProvider.js", + "type": "provider", + "provides": "telemetryService", + "depends": [ "$q", "$timeout" ] + } + ], + "types": [ + { + "key": "imagery", + "name": "Example Imagery", + "glyph": "T", + "features": "creation", + "model": { + "telemetry": {} + }, + "telemetry": { + "source": "imagery", + "domains": [ + { + "name": "Time", + "key": "time", + "format": "timestamp" + } + ], + "ranges": [ + { + "name": "Image", + "key": "url", + "format": "imageUrl" + } + ] + } + } + ] + } +} diff --git a/example/imagery/src/ImageTelemetry.js b/example/imagery/src/ImageTelemetry.js new file mode 100644 index 0000000000..447ddcbaa8 --- /dev/null +++ b/example/imagery/src/ImageTelemetry.js @@ -0,0 +1,66 @@ +/***************************************************************************** + * 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*/ + +/** + * Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14. + */ +define( + [], + function () { + "use strict"; + + var firstObservedTime = Date.now(), + images = [ + "http://www.nasa.gov/393811main_Palomar_ao_bouchez_10s_after_impact_4x3_946-710.png", + "http://www.nasa.gov/393821main_Palomar_ao_bouchez_15s_after_impact_4x3_946-710.png", + "http://www.nasa.gov/images/content/393801main_CfhtVeillet2_4x3_516-387.jpg", + "http://www.nasa.gov/images/content/392790main_1024_768_GeminiNorth_NightBeforeImpact_946-710.jpg" + ].map(function (url, index) { + return { + timestamp: firstObservedTime + 1000 * index, + url: url + }; + }); + + + /** + * + * @constructor + */ + function ImageTelemetry() { + return { + getPointCount: function () { + return Math.floor((Date.now() - firstObservedTime) / 1000); + }, + getDomainValue: function (i, domain) { + return images[i % images.length].timestamp; + }, + getRangeValue: function (i, range) { + return images[i % images.length].url; + } + }; + } + + return ImageTelemetry; + } +); diff --git a/example/imagery/src/ImageTelemetryProvider.js b/example/imagery/src/ImageTelemetryProvider.js new file mode 100644 index 0000000000..5dcd1e3e70 --- /dev/null +++ b/example/imagery/src/ImageTelemetryProvider.js @@ -0,0 +1,115 @@ +/***************************************************************************** + * 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*/ + +/** + * Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14. + */ +define( + ["./ImageTelemetry"], + function (ImageTelemetry) { + "use strict"; + + /** + * + * @constructor + */ + function ImageTelemetryProvider($q, $timeout) { + var subscriptions = []; + + // + function matchesSource(request) { + return request.source === "imagery"; + } + + // Used internally; this will be repacked by doPackage + function generateData(request) { + return { + key: request.key, + telemetry: new ImageTelemetry() + }; + } + + // + function doPackage(results) { + var packaged = {}; + results.forEach(function (result) { + packaged[result.key] = result.telemetry; + }); + // Format as expected (sources -> keys -> telemetry) + return { imagery: packaged }; + } + + function requestTelemetry(requests) { + return $timeout(function () { + return doPackage(requests.filter(matchesSource).map(generateData)); + }, 0); + } + + function handleSubscriptions() { + subscriptions.forEach(function (subscription) { + var requests = subscription.requests; + subscription.callback(doPackage( + requests.filter(matchesSource).map(generateData) + )); + }); + } + + function startGenerating() { + $timeout(function () { + handleSubscriptions(); + if (subscriptions.length > 0) { + startGenerating(); + } + }, 1000); + } + + function subscribe(callback, requests) { + var subscription = { + callback: callback, + requests: requests + }; + + function unsubscribe() { + subscriptions = subscriptions.filter(function (s) { + return s !== subscription; + }); + } + + subscriptions.push(subscription); + + if (subscriptions.length === 1) { + startGenerating(); + } + + return unsubscribe; + } + + return { + requestTelemetry: requestTelemetry, + subscribe: subscribe + }; + } + + return ImageTelemetryProvider; + } +); diff --git a/platform/features/imagery/bundle.json b/platform/features/imagery/bundle.json new file mode 100644 index 0000000000..8d8495aa3a --- /dev/null +++ b/platform/features/imagery/bundle.json @@ -0,0 +1,35 @@ +{ + "name": "Plot view for telemetry", + "extensions": { + "views": [ + { + "name": "Imagery", + "key": "imagery", + "glyph": "\u00E3", + "templateUrl": "templates/imagery.html", + "priority": "preferred", + "needs": [ "telemetry" ] + } + ], + "policies": [ + { + "category": "view", + "implementation": "policies/ImageryViewPolicy.js" + } + ], + "controllers": [ + { + "key": "ImageryController", + "implementation": "controllers/ImageryController.js", + "depends": [ "$scope", "telemetryHandler" ] + } + ], + "directives": [ + { + "key": "mctBackgroundImage", + "implementation": "directives/MCTBackgroundImage.js", + "depends": [ "$document" ] + } + ] + } +} diff --git a/platform/features/imagery/res/templates/imagery.html b/platform/features/imagery/res/templates/imagery.html new file mode 100644 index 0000000000..80f0813b2b --- /dev/null +++ b/platform/features/imagery/res/templates/imagery.html @@ -0,0 +1,67 @@ +
+ +
+ + +
+
+ +
+
+ + {{imagery.getZone()}} + {{imagery.getTime()}} + {{imagery.getDate()}} +
+
+ + + y + +
+
+
+
diff --git a/platform/features/imagery/src/controllers/ImageryController.js b/platform/features/imagery/src/controllers/ImageryController.js new file mode 100644 index 0000000000..72f72f39db --- /dev/null +++ b/platform/features/imagery/src/controllers/ImageryController.js @@ -0,0 +1,133 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + ['moment'], + function (moment) { + "use strict"; + + var DATE_FORMAT = "YYYY-MM-DD", + TIME_FORMAT = "HH:mm:ss.SSS"; + + /** + * Controller for the "Imagery" view of a domain object which + * provides image telemetry. + */ + function ImageryController($scope, telemetryHandler) { + var date = "", + time = "", + imageUrl = "", + paused = false, + handle; + + function releaseSubscription() { + if (handle) { + handle.unsubscribe(); + handle = undefined; + } + } + + function updateValues() { + var imageObject = handle && handle.getTelemetryObjects()[0], + m; + if (imageObject && !paused) { + m = moment.utc(handle.getDomainValue(imageObject)); + date = m.format(DATE_FORMAT); + time = m.format(TIME_FORMAT); + imageUrl = handle.getRangeValue(imageObject); + } + } + + // Create a new subscription; telemetrySubscriber gets + // to do the meaningful work here. + function subscribe(domainObject) { + releaseSubscription(); + date = ""; + time = ""; + imageUrl = ""; + handle = domainObject && telemetryHandler.handle( + domainObject, + updateValues, + true // Lossless + ); + } + + // Subscribe to telemetry when a domain object becomes available + $scope.$watch('domainObject', subscribe); + + // Unsubscribe when the plot is destroyed + $scope.$on("$destroy", releaseSubscription); + + return { + /** + * Get the time portion (hours, minutes, seconds) of the + * timestamp associated with the incoming image telemetry. + * @returns {string} the time + */ + getTime: function () { + return time; + }, + /** + * Get the date portion (month, year) of the + * timestamp associated with the incoming image telemetry. + * @returns {string} the date + */ + getDate: function () { + return date; + }, + /** + * Get the time zone for the displayed time/date corresponding + * to the timestamp associated with the incoming image + * telemetry. + * @returns {string} the time + */ + getZone: function () { + return "UTC"; + }, + /** + * Get the URL of the image telemetry to display. + * @returns {string} URL for telemetry image + */ + getImageUrl: function () { + return imageUrl; + }, + /** + * Getter-setter for paused state of the view (true means + * paused, false means not.) + * @param {boolean} [state] the state to set + * @returns {boolean} the current state + */ + paused: function (state) { + if (arguments.length > 0 && state !== paused) { + paused = state; + // Switch to latest image + updateValues(); + } + return paused; + } + }; + } + + return ImageryController; + } +); diff --git a/platform/features/imagery/src/directives/MCTBackgroundImage.js b/platform/features/imagery/src/directives/MCTBackgroundImage.js new file mode 100644 index 0000000000..0e5c20e919 --- /dev/null +++ b/platform/features/imagery/src/directives/MCTBackgroundImage.js @@ -0,0 +1,89 @@ +/***************************************************************************** + * 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"; + + /** + * Defines the `mct-background-image` directive. + * + * Used as an attribute, this will set the `background-image` + * property to the URL given in its value, but only after that + * image has loaded; this avoids "flashing" as images change. + * + * If `src` is falsy, no image will be displayed (immediately.) + * + */ + function MCTBackgroundImage($document) { + function link(scope, element, attrs) { + // General strategy here: + // - Keep count of how many images have been requested; this + // counter will be used as an internal identifier or sorts + // for each image that loads. + // - As the src attribute changes, begin loading those images. + // - When images do load, update the background-image property + // of the element, but only if a more recently + // requested image has not already been loaded. + // The order in which URLs are passed in and the order + // in which images are actually loaded may be different, so + // some strategy like this is necessary to ensure that images + // do not display out-of-order. + var div, requested = 0, loaded = 0; + + function nextImage(url) { + var myCounter = requested, + image; + + function useImage() { + if (loaded <= myCounter) { + loaded = myCounter; + element.css('background-image', "url('" + url + "')"); + } + } + + if (!url) { + loaded = myCounter; + element.css('background-image', undefined); + } else { + image = $document[0].createElement('img'); + image.src = url; + image.onload = useImage; + } + + requested += 1; + } + + scope.$watch('mctBackgroundImage', nextImage); + } + + return { + restrict: "A", + scope: { mctBackgroundImage: "=" }, + link: link + }; + } + + return MCTBackgroundImage; + } +); diff --git a/platform/features/imagery/src/policies/ImageryViewPolicy.js b/platform/features/imagery/src/policies/ImageryViewPolicy.js new file mode 100644 index 0000000000..40b74d96df --- /dev/null +++ b/platform/features/imagery/src/policies/ImageryViewPolicy.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * 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"; + + /** + * Policy preventing the Imagery view from being made available for + * domain objects which do not have associated image telemetry. + * @implements {Policy} + */ + function ImageryViewPolicy() { + function hasImageTelemetry(domainObject) { + var telemetry = domainObject && + domainObject.getCapability('telemetry'), + metadata = telemetry ? telemetry.getMetadata() : {}, + ranges = metadata.ranges || []; + + return ranges.some(function (range) { + return range.format === 'imageUrl' || + range.format === 'image'; + }); + } + + return { + allow: function (view, domainObject) { + if (view.key === 'imagery') { + return hasImageTelemetry(domainObject); + } + + return true; + } + }; + } + + return ImageryViewPolicy; + } +); diff --git a/platform/features/imagery/test/controllers/ImageryControllerSpec.js b/platform/features/imagery/test/controllers/ImageryControllerSpec.js new file mode 100644 index 0000000000..9bb00d6b20 --- /dev/null +++ b/platform/features/imagery/test/controllers/ImageryControllerSpec.js @@ -0,0 +1,152 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/controllers/ImageryController"], + function (ImageryController) { + "use strict"; + + describe("The Imagery controller", function () { + var mockScope, + mockTelemetryHandler, + mockHandle, + mockDomainObject, + controller; + + function invokeWatch(expr, value) { + mockScope.$watch.calls.forEach(function (call) { + if (call.args[0] === expr) { + call.args[1](value); + } + }); + } + + beforeEach(function () { + mockScope = jasmine.createSpyObj('$scope', ['$on', '$watch']); + mockTelemetryHandler = jasmine.createSpyObj( + 'telemetryHandler', + ['handle'] + ); + mockHandle = jasmine.createSpyObj( + 'handle', + [ + 'getDomainValue', + 'getRangeValue', + 'getTelemetryObjects', + 'unsubscribe' + ] + ); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + + mockTelemetryHandler.handle.andReturn(mockHandle); + mockHandle.getTelemetryObjects.andReturn([mockDomainObject]); + + controller = new ImageryController( + mockScope, + mockTelemetryHandler + ); + invokeWatch('domainObject', mockDomainObject); + }); + + it("unsubscribes when scope is destroyed", function () { + expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); + + // Find the $destroy listener and call it + mockScope.$on.calls.forEach(function (call) { + if (call.args[0] === '$destroy') { + call.args[1](); + } + }); + expect(mockHandle.unsubscribe).toHaveBeenCalled(); + }); + + it("exposes the latest telemetry values", function () { + // 06/18/2015 4:04am UTC + var testTimestamp = 1434600258123, + testUrl = "some/url", + nextTimestamp = 1434600259456, // 4:05.456 + nextUrl = "some/other/url"; + + mockHandle.getDomainValue.andReturn(testTimestamp); + mockHandle.getRangeValue.andReturn(testUrl); + + // Call the subscription listener + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + + mockHandle.getDomainValue.andReturn(nextTimestamp); + mockHandle.getRangeValue.andReturn(nextUrl); + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:19.456"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(nextUrl); + }); + + it("allows updates to be paused", function () { + // 06/18/2015 4:04am UTC + var testTimestamp = 1434600258123, + testUrl = "some/url", + nextTimestamp = 1434600259456, // 4:05.456 + nextUrl = "some/other/url"; + + // As above, but pause in between. Expect details + // not to change this time + + mockHandle.getDomainValue.andReturn(testTimestamp); + mockHandle.getRangeValue.andReturn(testUrl); + + // Call the subscription listener + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + + expect(controller.paused()).toBeFalsy(); + controller.paused(true); // Pause! + expect(controller.paused()).toBeTruthy(); + + mockHandle.getDomainValue.andReturn(nextTimestamp); + mockHandle.getRangeValue.andReturn(nextUrl); + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + }); + + }); + } +); + diff --git a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js new file mode 100644 index 0000000000..3c55f6f480 --- /dev/null +++ b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js @@ -0,0 +1,101 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/directives/MCTBackgroundImage"], + function (MCTBackgroundImage) { + "use strict"; + + describe("The mct-background-image directive", function () { + var mockDocument, + mockScope, + mockElement, + testImage, + directive; + + beforeEach(function () { + mockDocument = [ + jasmine.createSpyObj('document', ['createElement']) + ]; + mockScope = jasmine.createSpyObj('scope', ['$watch']); + mockElement = jasmine.createSpyObj('element', [ 'css' ]); + testImage = {}; + + mockDocument[0].createElement.andReturn(testImage); + + directive = new MCTBackgroundImage(mockDocument); + }); + + it("is applicable as an attribute", function () { + expect(directive.restrict).toEqual("A"); + }); + + it("two-way-binds its own value", function () { + expect(directive.scope.mctBackgroundImage).toEqual("="); + }); + + it("watches for changes to the URL", function () { + directive.link(mockScope, mockElement, {}); + expect(mockScope.$watch).toHaveBeenCalledWith( + 'mctBackgroundImage', + jasmine.any(Function) + ); + }); + + it("updates images in-order, even when they load out-of-order", function () { + var firstOnload; + + directive.link(mockScope, mockElement); + + mockScope.$watch.mostRecentCall.args[1]("some/url/0"); + firstOnload = testImage.onload; + + mockScope.$watch.mostRecentCall.args[1]("some/url/1"); + + // Resolve in a different order + testImage.onload(); + firstOnload(); + + // Should still have taken the more recent value + expect(mockElement.css.mostRecentCall.args).toEqual([ + "background-image", + "url('some/url/1')" + ]); + }); + + it("clears the background image when undefined is passed in", function () { + directive.link(mockScope, mockElement); + + mockScope.$watch.mostRecentCall.args[1]("some/url/0"); + testImage.onload(); + mockScope.$watch.mostRecentCall.args[1](undefined); + + expect(mockElement.css.mostRecentCall.args).toEqual([ + "background-image", + undefined + ]); + }); + }); + } +); + diff --git a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js new file mode 100644 index 0000000000..21e64cc324 --- /dev/null +++ b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js @@ -0,0 +1,80 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/policies/ImageryViewPolicy"], + function (ImageryViewPolicy) { + "use strict"; + + describe("Imagery view policy", function () { + var testView, + mockDomainObject, + mockTelemetry, + testMetadata, + policy; + + beforeEach(function () { + testView = { key: "imagery" }; + testMetadata = {}; + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + mockTelemetry = jasmine.createSpyObj( + 'telemetry', + ['getMetadata'] + ); + mockDomainObject.getCapability.andCallFake(function (c) { + return c === 'telemetry' ? mockTelemetry : undefined; + }); + mockTelemetry.getMetadata.andReturn(testMetadata); + + policy = new ImageryViewPolicy(); + }); + + it("allows the imagery view for domain objects with image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "imageUrl" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("disallows the imagery view for domain objects without image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("disallows the imagery view for domain objects without telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "imageUrl" } ]; + mockDomainObject.getCapability.andReturn(undefined); + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("allows other views", function () { + testView.key = "somethingElse"; + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + }); + } +); + diff --git a/platform/features/imagery/test/suite.json b/platform/features/imagery/test/suite.json new file mode 100644 index 0000000000..4ea822e7a2 --- /dev/null +++ b/platform/features/imagery/test/suite.json @@ -0,0 +1,5 @@ +[ + "controllers/ImageryController", + "directives/MCTBackgroundImage", + "policies/ImageryViewPolicy" +] diff --git a/platform/features/plot/bundle.json b/platform/features/plot/bundle.json index 2a7d6e9c73..bbbea34bdc 100644 --- a/platform/features/plot/bundle.json +++ b/platform/features/plot/bundle.json @@ -25,6 +25,12 @@ "implementation": "PlotController.js", "depends": [ "$scope", "telemetryFormatter", "telemetryHandler", "throttle" ] } + ], + "policies": [ + { + "category": "view", + "implementation": "policies/PlotViewPolicy.js" + } ] } -} \ No newline at end of file +} diff --git a/platform/features/plot/src/policies/PlotViewPolicy.js b/platform/features/plot/src/policies/PlotViewPolicy.js new file mode 100644 index 0000000000..78df8c3187 --- /dev/null +++ b/platform/features/plot/src/policies/PlotViewPolicy.js @@ -0,0 +1,65 @@ +/***************************************************************************** + * 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"; + + /** + * Policy preventing the Plot view from being made available for + * domain objects which have non-numeric telemetry. + * @implements {Policy} + */ + function PlotViewPolicy() { + function hasImageTelemetry(domainObject) { + var telemetry = domainObject && + domainObject.getCapability('telemetry'), + metadata = telemetry ? telemetry.getMetadata() : {}, + ranges = metadata.ranges || []; + + // Generally, we want to allow Plot for telemetry-providing + // objects (most telemetry is plottable.) We only want to + // suppress this for telemetry which only has explicitly + // non-numeric values. + return ranges.length === 0 || ranges.some(function (range) { + // Assume format is numeric if it is undefined + // (numeric telemetry is the common case) + return range.format === undefined || + range.format === 'number'; + }); + } + + return { + allow: function (view, domainObject) { + if (view.key === 'plot') { + return hasImageTelemetry(domainObject); + } + + return true; + } + }; + } + + return PlotViewPolicy; + } +); diff --git a/platform/features/plot/test/policies/PlotViewPolicySpec.js b/platform/features/plot/test/policies/PlotViewPolicySpec.js new file mode 100644 index 0000000000..27c40221ef --- /dev/null +++ b/platform/features/plot/test/policies/PlotViewPolicySpec.js @@ -0,0 +1,79 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine,xit*/ + +define( + ["../../src/policies/PlotViewPolicy"], + function (PlotViewPolicy) { + "use strict"; + + describe("Plot view policy", function () { + var testView, + mockDomainObject, + mockTelemetry, + testMetadata, + policy; + + beforeEach(function () { + testView = { key: "plot" }; + testMetadata = {}; + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + mockTelemetry = jasmine.createSpyObj( + 'telemetry', + ['getMetadata'] + ); + mockDomainObject.getCapability.andCallFake(function (c) { + return c === 'telemetry' ? mockTelemetry : undefined; + }); + mockTelemetry.getMetadata.andReturn(testMetadata); + + policy = new PlotViewPolicy(); + }); + + it("allows the imagery view for domain objects with numeric telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "number" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("allows the imagery view for domain objects with unspecified telemetry", function () { + testMetadata.ranges = [ { key: "foo" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("disallows the imagery view for domain objects without image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("allows other views", function () { + testView.key = "somethingElse"; + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + }); + } +); + diff --git a/platform/features/plot/test/suite.json b/platform/features/plot/test/suite.json index e3fa3fb796..323d53b6b3 100644 --- a/platform/features/plot/test/suite.json +++ b/platform/features/plot/test/suite.json @@ -18,6 +18,7 @@ "elements/PlotUpdater", "modes/PlotModeOptions", "modes/PlotOverlayMode", - "modes/PlotStackMode" + "modes/PlotStackMode", + "policies/PlotViewPolicy" ]