Files
openmct/src/plugins/plot/configuration/PlotConfigurationModel.js
Shefali Joshi b8949db767 Memory leak fixes for several views (#7057)
* Change the mount utility to use Vue's createApp and defineComponent methods

* Fix display layout memory leaks caused by `getSelectionContext`

* fix some display layout leaks due to use of slots

* Fix imagery memory leak (removed span tag). NOTE: CompassRose svg leaks memory - must test on firefox to see if this is a Chrome leak.

* Fix ActionsAPI action collection and applicable actions leak.

* Fix flexible layout memory leaks - remove listeners on unmount. NOTE: One type of overlay plot (Rover Yaw) is still leaking.

* pass in the el on mount

* e2e test config and spec changes

* Remove mounting of limit lines. Use components directly

* test: remove `.only()`

* Fix display layout memory leaks

* Enable passing tests

* e2e README and appActions should be what master has.

* lint: add word to cspell list

* lint: fixes

* lint:fix

* fix: revert `el` change

* fix: remove empty span

* fix: creating shapes in displayLayout

* fix: avoid `splice` as it loses reactivity

* test: reduce timeout time

* quick fixes

* add prod mode and convert the test config to select the correct mode

* Fix webpack prod config

* Add launch flag for exposing window.gc

* never worked

* explicit naming

* rename

* We don't need to destroy view providers

* test: increase timeout time

* test: unskip all mem tests

* fix(vue-loader): disable static hoisting

* chore: run `test:perf:memory`

* Don't destroy view providers

* Move context menu once listener to beforeUnmount instead.

* Disconnect all resize observers on unmount

* Delete Test vue component

* Use beforeUnmount and remove splice(0) in favor of [] for emptying arrays

* re-structure

* fix: unregister listener in pane.vue

* test: tweak timeouts

* chore: lint:fix

* test: unskip perf tests

* fix: unregister events properly

* fix: unregister listener

* fix: unregister listener

* fix: unregister listener

* fix: use `unmounted()`

* fix: unregister listeners

* fix: unregister listener properly

* chore: lint:fix

* test: fix imagery layer toggle test

* test: increase timeout

* Don't use anonymous functions for listeners

* Destroy objects and event listeners properly

* Delete config stores that are created by components

* Use the right unmount hook. Destroy mounted view on unmount.

* Use unmounted, not beforeUnmounted

* Lint fixes

* Fix time strip memory leak

* Undo unneeded change for memory leaks.

* chore: combine common webpack configs

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-09-20 10:34:05 -07:00

229 lines
7.2 KiB
JavaScript

/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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 _ from 'lodash';
import LegendModel from './LegendModel';
import Model from './Model';
import SeriesCollection from './SeriesCollection';
import XAxisModel from './XAxisModel';
import YAxisModel from './YAxisModel';
const MAX_Y_AXES = 3;
const MAIN_Y_AXES_ID = 1;
const MAX_ADDITIONAL_AXES = MAX_Y_AXES - 1;
/**
* PlotConfiguration model stores the configuration of a plot and some
* limited state. The individual parts of the plot configuration model
* handle setting defaults and updating in response to various changes.
*
* @extends {Model<PlotConfigModelType, PlotConfigModelOptions>}
*/
export default class PlotConfigurationModel extends Model {
/**
* Initializes all sub models and then passes references to submodels
* to those that need it.
*
* @override
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
*/
initialize(options) {
this.openmct = options.openmct;
// This is a type assertion for TypeScript, this error is never thrown in practice.
if (!options.model) {
throw new Error('Not a collection model.');
}
this.xAxis = new XAxisModel({
model: options.model.xAxis,
plot: this,
openmct: options.openmct
});
this.yAxis = new YAxisModel({
model: options.model.yAxis,
plot: this,
openmct: options.openmct,
id: options.model.yAxis.id || MAIN_Y_AXES_ID
});
//Add any axes in addition to the main yAxis above - we must always have at least 1 y-axis
//Addition axes ids will be the MAIN_Y_AXES_ID + x where x is between 1 and MAX_ADDITIONAL_AXES
this.additionalYAxes = [];
const hasAdditionalAxesConfiguration = Array.isArray(options.model.additionalYAxes);
for (let yAxisCount = 0; yAxisCount < MAX_ADDITIONAL_AXES; yAxisCount++) {
const yAxisId = MAIN_Y_AXES_ID + yAxisCount + 1;
const yAxis =
hasAdditionalAxesConfiguration &&
options.model.additionalYAxes.find((additionalYAxis) => additionalYAxis?.id === yAxisId);
if (yAxis) {
this.additionalYAxes.push(
new YAxisModel({
model: yAxis,
plot: this,
openmct: options.openmct,
id: yAxis.id
})
);
} else {
this.additionalYAxes.push(
new YAxisModel({
plot: this,
openmct: options.openmct,
id: yAxisId
})
);
}
}
// end add additional axes
this.legend = new LegendModel({
model: options.model.legend,
plot: this,
openmct: options.openmct
});
this.series = new SeriesCollection({
models: options.model.series,
plot: this,
openmct: options.openmct,
palette: options.palette
});
if (this.get('domainObject').type === 'telemetry.plot.overlay') {
this.removeMutationListener = this.openmct.objects.observe(
this.get('domainObject'),
'*',
this.updateDomainObject.bind(this)
);
}
this.yAxis.listenToSeriesCollection(this.series);
this.additionalYAxes.forEach((yAxis) => {
yAxis.listenToSeriesCollection(this.series);
});
this.legend.listenToSeriesCollection(this.series);
this.listenTo(this, 'destroy', this.onDestroy, this);
}
/**
* Retrieve the persisted series config for a given identifier.
* @param {import('./PlotSeries').Identifier} identifier
* @returns {import('./PlotSeries').PlotSeriesModelType=}
*/
getPersistedSeriesConfig(identifier) {
const domainObject = this.get('domainObject');
if (!domainObject.configuration || !domainObject.configuration.series) {
return;
}
return domainObject.configuration.series.filter(function (seriesConfig) {
return (
seriesConfig.identifier.key === identifier.key &&
seriesConfig.identifier.namespace === identifier.namespace
);
})[0];
}
/**
* Retrieve the persisted filters for a given identifier.
*/
getPersistedFilters(identifier) {
const domainObject = this.get('domainObject');
const keystring = this.openmct.objects.makeKeyString(identifier);
if (!domainObject.configuration || !domainObject.configuration.filters) {
return;
}
return domainObject.configuration.filters[keystring];
}
/**
* Update the domain object with the given value.
*/
updateDomainObject(domainObject) {
this.set('domainObject', domainObject);
}
/**
* Clean up all objects and remove all listeners.
*/
onDestroy() {
this.xAxis.destroy();
this.yAxis.destroy();
this.additionalYAxes.forEach((additionalYAxis) => additionalYAxis.destroy());
this.series.destroy();
this.legend.destroy();
this.stopListening();
if (this.removeMutationListener) {
this.removeMutationListener();
}
}
/**
* Return defaults, which are extracted from the passed in domain
* object.
* @override
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
*/
defaultModel(options) {
return {
series: [],
domainObject: options.domainObject,
xAxis: {},
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
additionalYAxes: _.cloneDeep(options.domainObject.configuration?.additionalYAxes ?? []),
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
};
}
}
/** @typedef {any} TODO */
/** @typedef {import('./PlotSeries').default} PlotSeries */
/**
@typedef {{
configuration: {
series: import('./PlotSeries').PlotSeriesModelType[]
yAxis: import('./YAxisModel').YAxisModelType
},
}} SomeDomainObject_NeedsName
*/
/**
@typedef {{
xAxis: import('./XAxisModel').XAxisModelType
yAxis: import('./YAxisModel').YAxisModelType
legend: TODO
series: PlotSeries[]
domainObject: SomeDomainObject_NeedsName
}} PlotConfigModelType
*/
/** @typedef {TODO} SomeOtherDomainObject */
/**
TODO: Is SomeOtherDomainObject the same domain object as with SomeDomainObject_NeedsName?
@typedef {{
plot: import('./PlotConfigurationModel').default
domainObject: SomeOtherDomainObject
}} PlotConfigModelOptions
*/