Compare commits

..

19 Commits

Author SHA1 Message Date
b062615c25 Merge branch 'master' into unload-openmct 2021-09-30 14:25:43 -07:00
4ba8f893a6 authors should think about backwards compatibility (#4223)
* authors should think about backwards compatibility

* Add reviewer check for breaking changes
2021-09-23 10:45:07 -05:00
e784242379 Fix leaking test specs 2021-09-10 17:21:09 -07:00
da8dbf25bf Make sure app holder gets destroyed 2021-09-10 17:20:54 -07:00
a5571eec05 Clean up router on destroy 2021-09-10 16:31:14 -07:00
19802ce2cc Fix merge error and restore missing bind statement 2021-09-10 16:30:49 -07:00
e83735a927 Clean up conductor resize handler on destroy 2021-09-10 16:24:35 -07:00
c9a47a411a Clean up notebook 2021-09-10 16:23:55 -07:00
a6e55fd493 Removed console logS 2021-09-10 16:17:38 -07:00
76f35a0bcd Merge recent changes to MCT.js from master 2021-09-10 16:17:12 -07:00
b6930ef7cd Merge branch 'master' into unload-openmct 2021-09-10 15:58:54 -07:00
57b9cbd42f Remove console log in Selection.js 2021-09-10 15:58:41 -07:00
17901147ef Fix broken tests 2021-09-10 15:50:44 -07:00
58b8f90682 Clean up MCT class 2021-09-10 15:50:36 -07:00
d06cbc8975 Destroy API listeners 2021-09-10 11:34:52 -07:00
553b18587c Clean up workers on destroy 2021-09-03 15:03:47 -07:00
590dc74e45 Fix memory leak in legacy ticker service 2021-09-03 14:46:23 -07:00
3557ed4b4c Fix memory leaks related to navigation 2021-09-03 13:36:51 -07:00
41cc52d50a Remove legacy routes support 2021-09-03 12:15:32 -07:00
53 changed files with 979 additions and 521 deletions

View File

@ -2,6 +2,7 @@
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
* [ ] Is this change backwards compatible? Will users need to change how they are calling the API, or how they've extended core plugins such as Tables or Plots?
### Author Checklist

View File

@ -317,6 +317,7 @@ checklist).
### Reviewer Checklist
* [ ] Changes appear to address issue?
* [ ] Changes appear not to be breaking changes?
* [ ] Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards?

View File

@ -41,11 +41,6 @@ define([
"$scope"
]
}
],
"routes": [
{
"templateUrl": "templates/exampleForm.html"
}
]
}
}

View File

@ -28,6 +28,15 @@ define([
domain: 2
}
},
{
key: "cos",
name: "Cosine",
unit: "deg",
formatString: '%0.2f',
hints: {
domain: 3
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
@ -109,6 +118,100 @@ define([
}
}
]
},
'example.spectral-generator': {
values: [
{
key: "name",
name: "Name",
format: "string"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "wavelength",
name: "Wavelength",
unit: "Hz",
formatString: '%0.2f',
hints: {
domain: 2,
spectralAttribute: true
}
},
{
key: "cos",
name: "Cosine",
unit: "deg",
formatString: '%0.2f',
hints: {
range: 2,
spectralAttribute: true
}
}
]
},
'example.spectral-aggregate-generator': {
values: [
{
key: "name",
name: "Name",
format: "string"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "ch1",
name: "Channel 1",
format: "string",
hints: {
range: 1
}
},
{
key: "ch2",
name: "Channel 2",
format: "string",
hints: {
range: 2
}
},
{
key: "ch3",
name: "Channel 3",
format: "string",
hints: {
range: 3
}
},
{
key: "ch4",
name: "Channel 4",
format: "string",
hints: {
range: 4
}
},
{
key: "ch5",
name: "Channel 5",
format: "string",
hints: {
range: 5
}
}
]
}
};

View File

@ -96,5 +96,9 @@ define([
return this.workerInterface.subscribe(workerRequest, callback);
};
GeneratorProvider.prototype.destroy = function () {
this.workerInterface.destroy();
};
return GeneratorProvider;
});

View File

@ -0,0 +1,86 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([
], function (
) {
function SpectralAggregateGeneratorProvider() {
}
function pointForTimestamp(timestamp, count, name) {
return {
name: name,
utc: String(Math.floor(timestamp / count) * count),
ch1: String(Math.floor(timestamp / count) % 1),
ch2: String(Math.floor(timestamp / count) % 2),
ch3: String(Math.floor(timestamp / count) % 3),
ch4: String(Math.floor(timestamp / count) % 4),
ch5: String(Math.floor(timestamp / count) % 5)
};
}
SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.spectral-aggregate-generator';
};
SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var count = 5000;
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, count, domainObject.name);
callback(datum);
}, count);
return function () {
clearInterval(interval);
};
};
SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.spectral-aggregate-generator';
};
SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var count = 5000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, count, domainObject.name));
start += count;
}
return Promise.resolve(data);
};
return SpectralAggregateGeneratorProvider;
});

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([
'./WorkerInterface'
], function (
WorkerInterface
) {
var REQUEST_DEFAULTS = {
amplitude: 1,
wavelength: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0
};
function SpectralGeneratorProvider() {
this.workerInterface = new WorkerInterface();
}
SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'example.spectral-generator';
};
SpectralGeneratorProvider.prototype.supportsRequest =
SpectralGeneratorProvider.prototype.supportsSubscribe =
SpectralGeneratorProvider.prototype.canProvideTelemetry;
SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request = {}) {
var props = [
'amplitude',
'wavelength',
'period',
'offset',
'dataRateInHz',
'phase',
'randomness'
];
var workerRequest = {};
props.forEach(function (prop) {
if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = request[prop];
}
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
workerRequest[prop] = Number(workerRequest[prop]);
});
workerRequest.name = domainObject.name;
return workerRequest;
};
SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
workerRequest.spectra = true;
return this.workerInterface.request(workerRequest);
};
SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var workerRequest = this.makeWorkerRequest(domainObject, {});
workerRequest.spectra = true;
return this.workerInterface.subscribe(workerRequest, callback);
};
return SpectralGeneratorProvider;
});

View File

@ -40,6 +40,11 @@ define([
this.callbacks = {};
}
WorkerInterface.prototype.destroy = function () {
delete this.worker.onmessage;
this.worker.terminate();
};
WorkerInterface.prototype.onMessage = function (message) {
message = message.data;
var callback = this.callbacks[message.id];

View File

@ -54,23 +54,38 @@
var start = Date.now();
var step = 1000 / data.dataRateInHz;
var nextStep = start - (start % step) + step;
let work;
if (data.spectra) {
work = function (now) {
while (nextStep < now) {
const messageCopy = Object.create(message);
message.data.start = nextStep - (60 * 1000);
message.data.end = nextStep;
onRequest(messageCopy);
nextStep += step;
}
function work(now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
return nextStep;
};
} else {
work = function (now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
return nextStep;
return nextStep;
};
}
subscriptions[message.id] = work;
@ -111,13 +126,21 @@
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
});
}
self.postMessage({
id: message.id,
data: data
data: request.spectra ? {
wavelength: data.map((item) => {
return item.wavelength;
}),
cos: data.map((item) => {
return item.cos;
})
} : data
});
}
@ -131,6 +154,10 @@
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
}
function wavelength(start, nextStep) {
return (nextStep - start) / 10;
}
function sendError(error, message) {
self.postMessage({
error: error.name + ': ' + error.message,

View File

@ -24,11 +24,15 @@ define([
"./GeneratorProvider",
"./SinewaveLimitProvider",
"./StateGeneratorProvider",
"./SpectralGeneratorProvider",
"./SpectralAggregateGeneratorProvider",
"./GeneratorMetadataProvider"
], function (
GeneratorProvider,
SinewaveLimitProvider,
StateGeneratorProvider,
SpectralGeneratorProvider,
SpectralAggregateGeneratorProvider,
GeneratorMetadataProvider
) {
@ -61,6 +65,37 @@ define([
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType("example.spectral-generator", {
name: "Spectral Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
cssClass: "icon-generator-telemetry",
creatable: true,
initialize: function (object) {
object.telemetry = {
period: 10,
amplitude: 1,
wavelength: 1,
frequency: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0
};
}
});
openmct.telemetry.addProvider(new SpectralGeneratorProvider());
openmct.types.addType("example.spectral-aggregate-generator", {
name: "Spectral Aggregate Generator",
description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
cssClass: "icon-generator-telemetry",
creatable: true,
initialize: function (object) {
object.telemetry = {};
}
});
openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
@ -146,7 +181,11 @@ define([
}
});
openmct.telemetry.addProvider(new GeneratorProvider());
const generatorProvider = new GeneratorProvider();
openmct.once('destroy', () => {
generatorProvider.destroy();
});
openmct.telemetry.addProvider(generatorProvider);
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
openmct.telemetry.addProvider(new SinewaveLimitProvider());
};

View File

@ -164,16 +164,6 @@ define([
"license": "license-apache",
"link": "http://logging.apache.org/log4net/license.html"
}
],
"routes": [
{
"when": "/licenses",
"template": licensesTemplate
},
{
"when": "/licenses-md",
"template": licensesExportMdTemplate
}
]
}
}

View File

@ -50,8 +50,6 @@ define([
name: "platform/commonUI/browse",
definition: {
"extensions": {
"routes": [
],
"constants": [
{
"key": "DEFAULT_PATH",

View File

@ -39,9 +39,6 @@ define(
this.callbacks = [];
this.checks = [];
this.$window = $window;
this.oldUnload = $window.onbeforeunload;
$window.onbeforeunload = this.onBeforeUnload.bind(this);
}
/**

View File

@ -71,7 +71,8 @@ define([
"implementation": TickerService,
"depends": [
"$timeout",
"now"
"now",
"$rootScope"
]
},
{

View File

@ -32,8 +32,13 @@ define(
* @param $timeout Angular's $timeout
* @param {Function} now function to provide the current time in ms
*/
function TickerService($timeout, now) {
function TickerService($timeout, now, $rootScope) {
var self = this;
var timeoutId;
$rootScope.$on('$destroy', function () {
$timeout.cancel(timeoutId);
});
function tick() {
var timestamp = now(),
@ -48,7 +53,7 @@ define(
}
// Try to update at exactly the next second
$timeout(tick, 1000 - millis, true);
timeoutId = $timeout(tick, 1000 - millis, true);
}
tick();

View File

@ -30,16 +30,18 @@ define(
var mockTimeout,
mockNow,
mockCallback,
tickerService;
tickerService,
mockRootScope;
beforeEach(function () {
mockTimeout = jasmine.createSpy('$timeout');
mockNow = jasmine.createSpy('now');
mockCallback = jasmine.createSpy('callback');
mockRootScope = jasmine.createSpyObj('rootScope', ['$on']);
mockNow.and.returnValue(TEST_TIMESTAMP);
tickerService = new TickerService(mockTimeout, mockNow);
tickerService = new TickerService(mockTimeout, mockNow, mockRootScope);
});
it("notifies listeners of clock ticks", function () {

View File

@ -58,7 +58,7 @@ define([
) {
var $http = this.$http,
$log = this.$log,
app = angular.module(Constants.MODULE_NAME, ["ngRoute"]),
app = angular.module(Constants.MODULE_NAME, []),
loader = new BundleLoader($http, $log, openmct.legacyRegistry),
resolver = new BundleResolver(
new ExtensionResolver(

View File

@ -28,8 +28,7 @@
define(
[
'./FrameworkLayer',
'angular',
'angular-route'
'angular'
],
function (
FrameworkLayer,

View File

@ -138,34 +138,6 @@ define(
}
}
// Custom registration function for extensions of category "route"
function registerRoute(extension) {
var app = this.app,
$log = this.$log,
route = Object.create(extension);
// Adjust path for bundle
if (route.templateUrl) {
route.templateUrl = [
route.bundle.path,
route.bundle.resources,
route.templateUrl
].join(Constants.SEPARATOR);
}
// Log the registration
$log.info("Registering route: " + (route.key || route.when));
// Register the route with Angular
app.config(['$routeProvider', function ($routeProvider) {
if (route.when) {
$routeProvider.when(route.when, route);
} else {
$routeProvider.otherwise(route);
}
}]);
}
// Handle service compositing
function registerComponents(components) {
var app = this.app,
@ -194,13 +166,6 @@ define(
CustomRegistrars.prototype.constants =
mapUpon(registerConstant);
/**
* Register Angular routes.
* @param {Array} extensions the resolved extensions
*/
CustomRegistrars.prototype.routes =
mapUpon(registerRoute);
/**
* Register Angular directives.
* @param {Array} extensions the resolved extensions

View File

@ -57,7 +57,6 @@ define(
expect(customRegistrars.directives).toBeTruthy();
expect(customRegistrars.controllers).toBeTruthy();
expect(customRegistrars.services).toBeTruthy();
expect(customRegistrars.routes).toBeTruthy();
expect(customRegistrars.constants).toBeTruthy();
expect(customRegistrars.runs).toBeTruthy();
});
@ -139,47 +138,6 @@ define(
expect(mockLog.warn.calls.count()).toEqual(0);
});
it("allows routes to be registered", function () {
var mockRouteProvider = jasmine.createSpyObj(
"$routeProvider",
["when", "otherwise"]
),
bundle = {
path: "test/bundle",
resources: "res"
},
routes = [
{
when: "foo",
templateUrl: "templates/test.html",
bundle: bundle
},
{
templateUrl: "templates/default.html",
bundle: bundle
}
];
customRegistrars.routes(routes);
// Give it the route provider based on its config call
mockApp.config.calls.all().forEach(function (call) {
// Invoke the provided callback
call.args[0][1](mockRouteProvider);
});
// The "when" clause should have been mapped to the when method...
expect(mockRouteProvider.when).toHaveBeenCalled();
expect(mockRouteProvider.when.calls.mostRecent().args[0]).toEqual("foo");
expect(mockRouteProvider.when.calls.mostRecent().args[1].templateUrl)
.toEqual("test/bundle/res/templates/test.html");
// ...while the other should have been treated as a default route
expect(mockRouteProvider.otherwise).toHaveBeenCalled();
expect(mockRouteProvider.otherwise.calls.mostRecent().args[0].templateUrl)
.toEqual("test/bundle/res/templates/default.html");
});
it("accepts components for service compositing", function () {
// Most relevant code will be exercised in service compositor spec
expect(customRegistrars.components).toBeTruthy();

View File

@ -110,8 +110,15 @@ define([
worker = workerService.run('bareBonesSearchWorker');
}
worker.addEventListener('message', function (messageEvent) {
function handleWorkerMessage(messageEvent) {
provider.onWorkerMessage(messageEvent);
}
worker.addEventListener('message', handleWorkerMessage);
this.openmct.once('destroy', () => {
worker.removeEventListener('message', handleWorkerMessage);
worker.terminate();
});
return worker;

View File

@ -31,7 +31,6 @@ define([
'objectUtils',
'./plugins/plugins',
'./adapter/indicators/legacy-indicators-plugin',
'./plugins/buildInfo/plugin',
'./ui/registries/ViewRegistry',
'./plugins/imagery/plugin',
'./ui/registries/InspectorViewRegistry',
@ -40,6 +39,7 @@ define([
'./ui/router/Browse',
'../platform/framework/src/Main',
'./ui/layout/Layout.vue',
'./ui/inspector/styles/StylesManager',
'../platform/core/src/objects/DomainObjectImpl',
'../platform/core/src/capabilities/ContextualDomainObject',
'./ui/preview/plugin',
@ -60,7 +60,6 @@ define([
objectUtils,
plugins,
LegacyIndicatorsPlugin,
buildInfoPlugin,
ViewRegistry,
ImageryPlugin,
InspectorViewRegistry,
@ -69,6 +68,7 @@ define([
Browse,
Main,
Layout,
stylesManager,
DomainObjectImpl,
ContextualDomainObject,
PreviewPlugin,
@ -123,6 +123,7 @@ define([
};
this.destroy = this.destroy.bind(this);
/**
* Tracks current selection state of the application.
* @private
@ -263,7 +264,6 @@ define([
// Plugins that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.Chart());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());
@ -377,6 +377,7 @@ define([
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
if (this.types.get('layout') === undefined) {
this.install(this.plugins.DisplayLayout({
showAsView: ['summary-widget']
@ -435,6 +436,9 @@ define([
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout.$refs.layout;
this.once('destroy', () => {
appLayout.$destroy();
});
Browse(this);
}
@ -463,9 +467,40 @@ define([
};
MCT.prototype.destroy = function () {
if (this._destroyed === true) {
return;
}
window.removeEventListener('beforeunload', this.destroy);
this.emit('destroy');
this.router.destroy();
this.removeAllListeners();
if (this.$injector) {
this.$injector.get('$rootScope').$destroy();
this.$injector = null;
}
if (this.$angular) {
this.$angular.element(this.element).off().removeData();
this.$angular.element(this.element).empty();
this.$angular = null;
}
this.overlays.destroy();
if (this.element) {
this.element.remove();
}
stylesManager.default.removeAllListeners();
window.angular = null;
window.openmct = null;
Object.keys(require.cache).forEach(key => delete require.cache[key]);
this._destroyed = true;
};
MCT.prototype.plugins = plugins;

View File

@ -32,6 +32,10 @@ define([
// cannot be injected.
function AlternateCompositionInitializer(openmct) {
AlternateCompositionCapability.appliesTo = function (model, id) {
openmct.once('destroy', () => {
delete AlternateCompositionCapability.appliesTo;
});
model = objectUtils.toNewFormat(model, id || '');
return Boolean(openmct.composition.get(model));

View File

@ -28,6 +28,10 @@ export default class Editor extends EventEmitter {
super();
this.editing = false;
this.openmct = openmct;
openmct.once('destroy', () => {
this.removeAllListeners();
});
}
/**

View File

@ -44,7 +44,15 @@ describe('The ActionCollection', () => {
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
openmct.$injector.get.and.callFake((key) => {
return {
'identifierService': mockIdentifierService,
'$rootScope': {
'$destroy': () => {}
}
}[key];
});
mockObjectPath = [
{
name: 'mock folder',

View File

@ -17,11 +17,7 @@ class OverlayAPI {
this.dismissLastOverlay = this.dismissLastOverlay.bind(this);
document.addEventListener('keyup', (event) => {
if (event.key === 'Escape') {
this.dismissLastOverlay();
}
});
document.addEventListener('keyup', this.dismissLastOverlay);
}
@ -52,10 +48,12 @@ class OverlayAPI {
/**
* private
*/
dismissLastOverlay() {
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (lastOverlay && lastOverlay.dismissable) {
lastOverlay.dismiss();
dismissLastOverlay(event) {
if (event.key === 'Escape') {
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (lastOverlay && lastOverlay.dismissable) {
lastOverlay.dismiss();
}
}
}
@ -63,7 +61,7 @@ class OverlayAPI {
* A description of option properties that can be passed into the overlay
* @typedef options
* @property {object} element DOMElement that is to be inserted/shown on the overlay
* @property {string} size preferred size of the overlay (large, small, fit)
* @property {string} size prefered size of the overlay (large, small, fit)
* @property {array} buttons optional button objects with label and callback properties
* @property {function} onDestroy callback to be called when overlay is destroyed
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
@ -132,6 +130,10 @@ class OverlayAPI {
return progressDialog;
}
destroy() {
document.removeEventListener('keyup', this.dismissLastOverlay);
}
}
export default OverlayAPI;

View File

@ -32,6 +32,10 @@ export default class StatusAPI extends EventEmitter {
this.get = this.get.bind(this);
this.set = this.set.bind(this);
this.observe = this.observe.bind(this);
openmct.once('destroy', () => {
this.removeAllListeners();
});
}
get(identifier) {

View File

@ -1,49 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
import { BAR_GRAPH_KEY } from '../charts/barGraph/BarGraphConstants';
import BarGraphViewProvider from '../charts/barGraph/BarGraphViewProvider';
import BarGraphInspectorViewProvider from '../charts/barGraph/inspector/BarGraphInspectorViewProvider';
import BarGraphCompositionPolicy from '../charts/barGraph/BarGraphCompositionPolicy';
export default function () {
return function install(openmct) {
openmct.types.addType(BAR_GRAPH_KEY, {
key: BAR_GRAPH_KEY,
name: "Bar Graph",
cssClass: "icon-bar-chart",
description: "View data as a bar graph. Can be added to Display Layouts.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
barStyles: {}
};
},
priority: 891
});
openmct.objectViews.addProvider(new BarGraphViewProvider(openmct));
openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(openmct));
openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
};
}

View File

@ -1,151 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
import {createOpenMct, resetApplicationState} from "utils/testing";
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from '../charts/barGraph/BarGraphConstants';
import BarGraphPlugin from './plugin';
describe("the plugin", function () {
let element;
let child;
let openmct;
let telemetryPromise;
let telemetryPromiseResolve;
let mockObjectPath;
beforeEach((done) => {
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'time-strip',
identifier: {
key: 'mock-parent-folder',
namespace: ''
}
}
];
const testTelemetry = [
{
'utc': 1,
'some-key': 'some-value 1',
'some-other-key': 'some-other-value 1'
},
{
'utc': 2,
'some-key': 'some-value 2',
'some-other-key': 'some-other-value 2'
},
{
'utc': 3,
'some-key': 'some-value 3',
'some-other-key': 'some-other-value 3'
}
];
openmct = createOpenMct();
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
});
openmct.install(new BarGraphPlugin());
element = document.createElement("div");
element.style.width = "640px";
element.style.height = "480px";
child = document.createElement("div");
child.style.width = "640px";
child.style.height = "480px";
element.appendChild(child);
document.body.appendChild(element);
spyOn(window, 'ResizeObserver').and.returnValue({
observe() {},
disconnect() {}
});
openmct.time.timeSystem("utc", {
start: 0,
end: 4
});
openmct.types.addType("test-object", {
creatable: true
});
openmct.on("start", done);
openmct.startHeadless();
});
afterEach((done) => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
resetApplicationState(openmct).then(done).catch(done);
});
describe("the bar graph", () => {
const mockObject = {
name: 'A bar graph',
key: BAR_GRAPH_KEY,
creatable: true
};
it('defines a bar graph type with the correct key', () => {
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
expect(objectDef.key).toEqual(mockObject.key);
});
it('is creatable', () => {
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
describe("the bar graph view", () => {
it("provides view for objects with telemetry", () => {
const testObject = {
id: "test-object",
type: BAR_GRAPH_KEY
};
const applicableViews = openmct.objectViews.get(testObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
expect(plotView).toBeDefined();
});
});
});

View File

@ -75,7 +75,16 @@ describe("the plugin", () => {
mockDialogService.getUserInput.and.returnValue(mockPromise);
spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService);
spyOn(openmct.$injector, 'get');
openmct.$injector.get.and.callFake((key) => {
return {
'dialogService': mockDialogService,
'$rootScope': {
'$destroy': () => {}
}
}[key];
});
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));

View File

@ -117,6 +117,10 @@ export default function NotebookPlugin() {
key: 'notebook-snapshot-indicator'
};
openmct.once('destroy', () => {
snapshotContainer.destroy();
});
openmct.indicators.add(indicator);
openmct.objectViews.addProvider({

View File

@ -85,4 +85,8 @@ export default class SnapshotContainer extends EventEmitter {
return this.saveSnapshots(updatedSnapshots);
}
destroy() {
delete SnapshotContainer.instance;
}
}

View File

@ -22,95 +22,109 @@
import * as NotebookEntries from './notebook-entries';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
defaultSectionId: '03a79b6a-971c-4e56-9892-ec536332c3f0',
defaultPageId: '8b548fd9-2b8a-4b02-93a9-4138e22eba00'
};
const notebookEntries = {
'03a79b6a-971c-4e56-9892-ec536332c3f0': {
'8b548fd9-2b8a-4b02-93a9-4138e22eba00': []
}
};
const notebookDomainObject = {
identifier: {
key: 'notebook',
namespace: ''
},
type: 'notebook',
configuration: {
defaultSort: 'oldest',
entries: notebookEntries,
pageTitle: 'Page',
sections: [],
sectionTitle: 'Section',
type: 'General'
}
};
const selectedSection = {
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
isDefault: false,
isSelected: true,
name: 'Day 1',
pages: [
{
id: '54deb3d5-8267-4be4-95e9-3579ed8c082d',
isDefault: false,
isSelected: false,
name: 'Shift 1',
pageTitle: 'Page'
},
{
id: '2ea41c78-8e60-4657-a350-53f1a1fa3021',
isDefault: false,
isSelected: false,
name: 'Shift 2',
pageTitle: 'Page'
},
{
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
const selectedPage = {
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
};
let notebookStorage;
let notebookEntries;
let notebookDomainObject;
let selectedSection;
let selectedPage;
let openmct;
let mockIdentifierService;
describe('Notebook Entries:', () => {
beforeEach(() => {
notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
defaultSectionId: '03a79b6a-971c-4e56-9892-ec536332c3f0',
defaultPageId: '8b548fd9-2b8a-4b02-93a9-4138e22eba00'
};
notebookEntries = {
'03a79b6a-971c-4e56-9892-ec536332c3f0': {
'8b548fd9-2b8a-4b02-93a9-4138e22eba00': []
}
};
notebookDomainObject = {
identifier: {
key: 'notebook',
namespace: ''
},
type: 'notebook',
configuration: {
defaultSort: 'oldest',
entries: notebookEntries,
pageTitle: 'Page',
sections: [],
sectionTitle: 'Section',
type: 'General'
}
};
selectedSection = {
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
isDefault: false,
isSelected: true,
name: 'Day 1',
pages: [
{
id: '54deb3d5-8267-4be4-95e9-3579ed8c082d',
isDefault: false,
isSelected: false,
name: 'Shift 1',
pageTitle: 'Page'
},
{
id: '2ea41c78-8e60-4657-a350-53f1a1fa3021',
isDefault: false,
isSelected: false,
name: 'Shift 2',
pageTitle: 'Page'
},
{
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
selectedPage = {
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
};
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
openmct.$injector.get.and.callFake((key) => {
return {
'identifierService': mockIdentifierService,
'$rootScope': {
'$destroy': () => {}
}
}[key];
});
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return '';
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
openmct.types.addType('notebook', {
creatable: true
});

View File

@ -23,51 +23,55 @@
import * as NotebookStorage from './notebook-storage';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookSection = {
id: 'temp-section',
isDefault: false,
isSelected: true,
name: 'section',
pages: [
{
id: 'temp-page',
isDefault: false,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
const domainObject = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
configuration: {
sections: [
notebookSection
]
}
};
const notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
defaultSectionId: 'temp-section',
defaultPageId: 'temp-page'
};
let notebookSection;
let domainObject;
let notebookStorage;
let openmct;
let mockIdentifierService;
describe('Notebook Storage:', () => {
beforeEach(() => {
notebookSection = {
id: 'temp-section',
isDefault: false,
isSelected: true,
name: 'section',
pages: [
{
id: 'temp-page',
isDefault: false,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
domainObject = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
configuration: {
sections: [
notebookSection
]
}
};
notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
defaultSectionId: 'temp-section',
defaultPageId: 'temp-page'
};
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
@ -80,7 +84,15 @@ describe('Notebook Storage:', () => {
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
openmct.$injector.get.and.callFake((key) => {
return {
'identifierService': mockIdentifierService,
'$rootScope': {
'$destroy': () => {}
}
}[key];
});
window.localStorage.setItem('notebook-storage', null);
openmct.objects.addProvider('', jasmine.createSpyObj('mockNotebookProvider', [
'create',

View File

@ -29,13 +29,8 @@ describe('the plugin', function () {
let element;
let child;
let openmct;
let appHolder;
beforeEach((done) => {
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
openmct.install(new PlanPlugin());
@ -50,7 +45,7 @@ describe('the plugin', function () {
element.appendChild(child);
openmct.on('start', done);
openmct.start(appHolder);
openmct.start(element);
});
afterEach(() => {
@ -99,6 +94,7 @@ describe('the plugin', function () {
}
];
let planView;
let view;
beforeEach(() => {
openmct.time.timeSystem('utc', {
@ -139,12 +135,16 @@ describe('the plugin', function () {
const applicableViews = openmct.objectViews.get(planDomainObject, []);
planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
let view = planView.view(planDomainObject, mockObjectPath);
view = planView.view(planDomainObject, mockObjectPath);
view.show(child, true);
return Vue.nextTick();
});
afterEach(() => {
view.destroy();
});
it('loads activities into the view', () => {
const svgEls = element.querySelectorAll('.c-plan__contents svg');
expect(svgEls.length).toEqual(1);

View File

@ -23,20 +23,24 @@
import { BAR_GRAPH_KEY } from './BarGraphConstants';
export default function BarGraphCompositionPolicy(openmct) {
function hasRange(metadata) {
function hasAggregateDomainAndRange(metadata) {
const rangeValues = metadata.valuesForHints(['range']);
return rangeValues.length > 0;
}
function hasBarGraphTelemetry(domainObject) {
if (!openmct.telemetry.isTelemetryObject(domainObject)) {
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasRange(metadata);
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
}
function hasNoChildren(parentObject) {
return parentObject.composition && parentObject.composition.length < 1;
}
return {

View File

@ -1,3 +1,5 @@
export const BAR_GRAPH_VIEW = 'bar-graph.view';
export const BAR_GRAPH_KEY = 'telemetry.plot.bar-graph';
export const BAR_GRAPH_INSPECTOR_KEY = 'telemetry.plot.bar-graph.inspector';
export const SUBSCRIBE = 'subscribe';
export const UNSUBSCRIBE = 'unsubscribe';

View File

@ -12,7 +12,6 @@
</div>
<div ref="plot"
class="c-bar-chart"
@plotly_relayout="zoom"
></div>
<div v-if="false"
ref="localControl"
@ -29,7 +28,8 @@
</div>
</template>
<script>
import Plotly from 'plotly-basic';
import Plotly from 'plotly.js-basic-dist';
import { SUBSCRIBE, UNSUBSCRIBE } from './BarGraphConstants';
const MULTI_AXES_X_PADDING_PERCENT = {
LEFT: 8,
@ -79,6 +79,8 @@ export default {
this.registerListeners();
},
beforeDestroy() {
this.$refs.plot.removeAllListeners();
if (this.plotResizeObserver) {
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
clearTimeout(this.resizeTimer);
@ -201,6 +203,8 @@ export default {
return yaxis;
},
registerListeners() {
this.$refs.plot.on('plotly_relayout', this.zoom);
this.removeBarColorListener = this.openmct.objects.observe(
this.domainObject,
'configuration.barStyles',
@ -222,7 +226,7 @@ export default {
this.updatePlot();
this.isZoomed = false;
this.$emit('subscribe');
this.$emit(SUBSCRIBE);
},
barColorChanged() {
const colors = [];
@ -281,7 +285,7 @@ export default {
}
this.isZoomed = true;
this.$emit('unsubscribe');
this.$emit(UNSUBSCRIBE);
}
}
};

View File

@ -25,13 +25,12 @@
class="c-plot c-bar-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
/>
</template>
<script>
import ColorPalette from '../../plot/lib/ColorPalette';
import * as SPECTRAL_AGGREGATE from './BarGraphConstants';
import ColorPalette from '../lib/ColorPalette';
import BarGraph from './BarGraphPlot.vue';
import Color from "@/plugins/plot/lib/Color";
@ -41,16 +40,18 @@ export default {
},
inject: ['openmct', 'domainObject'],
data() {
this.telemetryObjects = {};
this.telemetryObjectFormats = {};
this.subscriptions = [];
this.composition = {};
return {
composition: {},
currentDomainObject: this.domainObject,
subscriptions: [],
telemetryObjects: {},
trace: []
};
},
computed: {
activeClock() {
return this.openmct.time.activeClock;
},
plotAxisTitle() {
const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
@ -67,11 +68,20 @@ export default {
this.loadComposition();
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('clock', this.clockChanged);
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll);
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions);
this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.$refs.barGraph.$off();
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('clock', this.clockChanged);
this.removeAllSubscriptions();
this.unobserve();
if (!this.composition) {
return;
@ -84,16 +94,21 @@ export default {
addTelemetryObject(telemetryObject) {
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (!this.domainObject.configuration.barStyles) {
this.domainObject.configuration.barStyles = {};
}
// check to see if we've set a bar color
if (!this.domainObject.configuration.barStyles[key] || !this.domainObject.configuration.barStyles[key].color) {
const color = this.colorPalette.getNextColor().asHexString();
this.domainObject.configuration.barStyles[key] = {
name: telemetryObject.name,
color
};
this.openmct.objects.mutate(
this.domainObject,
`configuration.barStyles[${key}]`,
{
name: telemetryObject.name,
color
}
`configuration.barStyles[${this.key}]`,
this.domainObject.configuration.barStyles[key]
);
} else {
let color = this.domainObject.configuration.barStyles[key].color;
@ -105,9 +120,6 @@ export default {
}
this.telemetryObjects[key] = telemetryObject;
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
const formats = this.openmct.telemetry.getFormatMap(metadata);
this.telemetryObjectFormats[key] = formats;
this.requestDataFor(telemetryObject);
this.subscribeToObject(telemetryObject);
@ -132,6 +144,10 @@ export default {
this.trace = isInTrace ? newTrace : newTrace.concat([trace]);
},
clockChanged() {
this.removeAllSubscriptions();
this.subscribeToAll();
},
getAxisMetadata(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
@ -143,19 +159,21 @@ export default {
yAxisMetadata
};
},
getOptions() {
getOptions(telemetryObject) {
const { start, end } = this.openmct.time.bounds();
return {
end,
start
start,
startTime: null,
spectra: true
};
},
loadComposition() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition = this.openmct.composition.get(this.currentDomainObject);
if (!this.composition) {
this.addTelemetryObject(this.domainObject);
this.addTelemetryObject(this.currentDomainObject);
return;
}
@ -184,7 +202,6 @@ export default {
removeTelemetryObject(identifier) {
const key = this.openmct.objects.makeKeyString(identifier);
delete this.telemetryObjects[key];
delete this.this.telemetryObjectFormats[key];
if (this.domainObject.configuration.barStyles[key]) {
delete this.domainObject.configuration.barStyles[key];
}
@ -193,17 +210,13 @@ export default {
this.trace = this.trace.filter(t => t.key !== key);
},
addDataToGraph(telemetryObject, data, axisMetadata) {
processData(telemetryObject, data, axisMetadata) {
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (data.message) {
this.openmct.notifications.alert(data.message);
}
if (!this.isDataInTimeRange(data)) {
return;
}
let xValues = [];
let yValues = [];
@ -211,10 +224,10 @@ export default {
axisMetadata.xAxisMetadata.forEach((metadata) => {
xValues.push(metadata.name);
if (data[metadata.key]) {
const formattedValue = this.format(key, metadata.key, data);
yValues.push(formattedValue);
//TODO: Format the data?
yValues.push(data[metadata.key]);
} else {
yValues.push(null);
yValues.push('');
}
});
@ -235,23 +248,12 @@ export default {
this.addTrace(trace, key);
},
isDataInTimeRange(data) {
const timeSystemKey = this.openmct.time.timeSystem().key;
const currentTimestamp = data[timeSystemKey];
return currentTimestamp && this.openmct.time.bounds().end >= currentTimestamp;
},
format(telemetryObjectKey, metadataKey, data) {
const formats = this.telemetryObjectFormats[telemetryObjectKey];
return formats[metadataKey].format(data);
},
requestDataFor(telemetryObject) {
const axisMetadata = this.getAxisMetadata(telemetryObject);
this.openmct.telemetry.request(telemetryObject)
this.openmct.telemetry.request(telemetryObject, this.getOptions(telemetryObject))
.then(data => {
data.forEach((datum) => {
this.addDataToGraph(telemetryObject, datum, axisMetadata);
this.processData(telemetryObject, datum, axisMetadata);
});
});
},
@ -260,10 +262,10 @@ export default {
this.removeSubscription(key);
const options = this.getOptions();
const options = this.getOptions(telemetryObject);
const axisMetadata = this.getAxisMetadata(telemetryObject);
const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
data => this.addDataToGraph(telemetryObject, data, axisMetadata)
data => this.processData(telemetryObject, data, axisMetadata)
, options);
this.subscriptions.push({
@ -274,6 +276,9 @@ export default {
subscribeToAll() {
const telemetryObjects = Object.values(this.telemetryObjects);
telemetryObjects.forEach(this.subscribeToObject);
},
updateDomainObject(newDomainObject) {
this.currentDomainObject = newDomainObject;
}
}
};

View File

@ -44,7 +44,7 @@
</template>
<script>
import ColorSwatch from '../../../plot/ColorSwatch.vue';
import ColorSwatch from '../../ColorSwatch.vue';
export default {
components: {

View File

@ -19,12 +19,18 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
import PlotViewProvider from './PlotViewProvider';
import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider';
import BarGraphViewProvider from './barGraph/BarGraphViewProvider';
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
import BarGraphInspectorViewProvider from './barGraph/inspector/BarGraphInspectorViewProvider';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy';
import BarGraphCompositionPolicy from './barGraph/BarGraphCompositionPolicy';
export default function () {
return function install(openmct) {
@ -58,15 +64,48 @@ export default function () {
},
priority: 890
});
openmct.types.addType('telemetry.plot.spectral', {
key: "telemetry.plot.spectral",
name: "Spectral Plot",
cssClass: "icon-plot-stacked",
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
//Temporarily disabling spectral plots
creatable: false,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {};
},
priority: 890
});
openmct.types.addType(BAR_GRAPH_KEY, {
key: BAR_GRAPH_KEY,
name: "Bar Graph",
cssClass: "icon-bar-chart",
description: "View data as a bar graph. Can be added to Display Layouts.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
plotType: 'bar'
};
},
priority: 891
});
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
openmct.objectViews.addProvider(new SpectralPlotViewProvider(openmct));
openmct.objectViews.addProvider(new BarGraphViewProvider(openmct));
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(openmct));
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new SpectralPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
};
}

View File

@ -24,10 +24,12 @@ import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} f
import PlotVuePlugin from "./plugin";
import Vue from "vue";
import StackedPlot from "./stackedPlot/StackedPlot.vue";
// import SpectralPlot from "./spectralPlot/SpectralPlot.vue";
import configStore from "./configuration/ConfigStore";
import EventEmitter from "EventEmitter";
import PlotOptions from "./inspector/PlotOptions.vue";
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
describe("the plugin", function () {
let element;
@ -312,6 +314,38 @@ describe("the plugin", function () {
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
expect(plotView).toBeDefined();
});
it("provides a spectral plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: "telemetry.plot.spectral",
telemetry: {
values: [{
key: "a-very-fine-key"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-spectral");
expect(plotView).toBeDefined();
});
it("provides a spectral aggregate plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: BAR_GRAPH_KEY,
telemetry: {
values: [{
key: "lots-of-aggregate-telemetry"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
expect(plotView).toBeDefined();
});
});
describe("The single plot view", () => {
@ -462,6 +496,146 @@ describe("the plugin", function () {
});
});
/*
* disabling this until we develop the plot view
describe("The spectral plot view", () => {
let testTelemetryObject;
// eslint-disable-next-line no-unused-vars
let testTelemetryObject2;
// eslint-disable-next-line no-unused-vars
let config;
let spectralPlotObject;
let component;
let mockComposition;
// eslint-disable-next-line no-unused-vars
let plotViewComponentObject;
beforeEach(() => {
const getFunc = openmct.$injector.get;
spyOn(openmct.$injector, "get")
.withArgs("exportImageService").and.returnValue({
exportPNG: () => {},
exportJPG: () => {}
})
.and.callFake(getFunc);
spectralPlotObject = {
identifier: {
namespace: "",
key: "test-spectral-plot"
},
type: "telemetry.plot.spectral",
name: "Test Spectral Plot"
};
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "wavelength",
name: "Wavelength",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
SpectralPlot
},
provide: {
openmct: openmct,
domainObject: spectralPlotObject,
composition: openmct.composition.get(spectralPlotObject)
},
template: "<spectral-plot></spectral-plot>"
});
cleanupFirst.push(() => {
component.$destroy();
component = undefined;
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
}); */
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
@ -991,4 +1165,39 @@ describe("the plugin", function () {
});
});
describe("the spectral plot", () => {
const mockObject = {
name: 'A Very Nice Spectral Plot',
key: 'telemetry.plot.spectral',
creatable: true
};
it('defines a spectral plot object type with the correct key', () => {
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
expect(objectDef.key).toEqual(mockObject.key);
});
xit('is creatable', () => {
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
describe("the aggregate spectral plot", () => {
const mockObject = {
name: 'An Even Nicer Aggregate Spectral Plot',
key: BAR_GRAPH_KEY,
creatable: true
};
it('defines a spectral plot object type with the correct key', () => {
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
expect(objectDef.key).toEqual(mockObject.key);
});
it('is creatable', () => {
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
});

View File

@ -36,7 +36,6 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./charts/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
@ -88,7 +87,6 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
ChartPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
@ -191,7 +189,6 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin.default;
plugins.Chart = ChartPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;

View File

@ -98,7 +98,10 @@ export default {
//Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL);
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
},
destroyed() {
clearInterval(this.resizeTimer);
},
methods: {
setAxisDimensions() {

View File

@ -38,6 +38,10 @@ define(
this.openmct = openmct;
this.selected = [];
this.openmct.once('destroy', () => {
this.removeAllListeners();
});
}
Selection.prototype = Object.create(EventEmitter.prototype);

View File

@ -54,6 +54,9 @@ describe("the inspector", () => {
});
afterEach(() => {
stylesViewComponent.$destroy();
savedStylesViewComponent.$destroy();
return resetApplicationState(openmct);
});
@ -77,7 +80,7 @@ describe("the inspector", () => {
expect(savedStylesViewComponent.$children[0].$children.length).toBe(0);
stylesViewComponent.$children[0].saveStyle(mockStyle);
stylesViewComponent.$nextTick().then(() => {
return stylesViewComponent.$nextTick().then(() => {
expect(savedStylesViewComponent.$children[0].$children.length).toBe(1);
});
});
@ -91,7 +94,7 @@ describe("the inspector", () => {
stylesViewComponent.$children[0].saveStyle(mockStyle);
stylesViewComponent.$nextTick().then(() => {
return stylesViewComponent.$nextTick().then(() => {
const styleSelectorComponent = savedStylesViewComponent.$children[0].$children[0];
styleSelectorComponent.selectStyle();
@ -147,7 +150,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => {
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;
@ -168,7 +171,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => {
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;
@ -185,7 +188,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => {
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;

View File

@ -134,7 +134,7 @@ export default {
actionCollection: {
type: Object,
default: () => {
return {};
return undefined;
}
}
},
@ -233,6 +233,7 @@ export default {
},
mounted: function () {
document.addEventListener('click', this.closeViewAndSaveMenu);
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);

View File

@ -49,6 +49,10 @@ class ApplicationRouter extends EventEmitter {
this.routes = [];
this.started = false;
openmct.once('destroy', () => {
this.destroy();
});
this.setHash = _.debounce(this.setHash.bind(this), 300);
}

View File

@ -42,8 +42,6 @@ const webpackConfig = {
"csv": "comma-separated-values",
"EventEmitter": "eventemitter3",
"bourbon": "bourbon.scss",
"plotly-basic": "plotly.js-basic-dist",
"plotly-gl2d": "plotly.js-gl2d-dist",
"vue": vueFile,
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),