Shefali Joshi b8949db767
Memory leak fixes for several views ()
* 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

343 lines
11 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 EventEmitter from 'EventEmitter';
import { v4 as uuid } from 'uuid';
import AllTelemetryCriterion from './criterion/AllTelemetryCriterion';
import TelemetryCriterion from './criterion/TelemetryCriterion';
import { TRIGGER_CONJUNCTION, TRIGGER_LABEL } from './utils/constants';
import { evaluateResults } from './utils/evaluator';
import { getLatestTimestamp } from './utils/time';
/*
* conditionConfiguration = {
* id: uuid,
* trigger: 'any'/'all'/'not','xor',
* criteria: [
* {
* telemetry: '',
* operation: '',
* input: [],
* metadata: ''
* }
* ]
* }
*/
export default class Condition extends EventEmitter {
/**
* Manages criteria and emits the result of - true or false - based on criteria evaluated.
* @constructor
* @param conditionConfiguration: {id: uuid,trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} }
* @param openmct
* @param conditionManager
*/
constructor(conditionConfiguration, openmct, conditionManager) {
super();
this.openmct = openmct;
this.conditionManager = conditionManager;
this.id = conditionConfiguration.id;
this.criteria = [];
this.result = undefined;
this.timeSystems = this.openmct.time.getAllTimeSystems();
if (conditionConfiguration.configuration.criteria) {
this.createCriteria(conditionConfiguration.configuration.criteria);
}
this.trigger = conditionConfiguration.configuration.trigger;
this.summary = '';
this.handleCriterionUpdated = this.handleCriterionUpdated.bind(this);
this.handleOldTelemetryCriterion = this.handleOldTelemetryCriterion.bind(this);
this.handleTelemetryStaleness = this.handleTelemetryStaleness.bind(this);
}
updateResult(datum) {
if (!datum || !datum.id) {
console.log('no data received');
return;
}
// if all the criteria in this condition have no telemetry, we want to force the condition result to evaluate
if (this.hasNoTelemetry() || this.isTelemetryUsed(datum.id)) {
this.criteria.forEach((criterion) => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
} else {
if (criterion.usesTelemetry(datum.id)) {
criterion.updateResult(datum);
}
}
});
this.result = evaluateResults(
this.criteria.map((criterion) => criterion.result),
this.trigger
);
}
}
isAnyOrAllTelemetry(criterion) {
return criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any');
}
hasNoTelemetry() {
return this.criteria.every((criterion) => {
return !this.isAnyOrAllTelemetry(criterion) && criterion.telemetry === '';
});
}
isTelemetryUsed(id) {
return this.criteria.some((criterion) => {
return this.isAnyOrAllTelemetry(criterion) || criterion.usesTelemetry(id);
});
}
update(conditionConfiguration) {
this.updateTrigger(conditionConfiguration.configuration.trigger);
this.updateCriteria(conditionConfiguration.configuration.criteria);
}
updateTrigger(trigger) {
if (this.trigger !== trigger) {
this.trigger = trigger;
}
}
generateCriterion(criterionConfiguration) {
return {
id: criterionConfiguration.id || uuid(),
telemetry: criterionConfiguration.telemetry || '',
telemetryObjects: this.conditionManager.telemetryObjects,
operation: criterionConfiguration.operation || '',
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
metadata: criterionConfiguration.metadata || ''
};
}
createCriteria(criterionConfigurations) {
criterionConfigurations.forEach((criterionConfiguration) => {
this.addCriterion(criterionConfiguration);
});
}
updateCriteria(criterionConfigurations) {
this.destroyCriteria();
this.createCriteria(criterionConfigurations);
}
updateTelemetryObjects() {
this.criteria.forEach((criterion) => {
criterion.updateTelemetryObjects(this.conditionManager.telemetryObjects);
});
}
/**
* adds criterion to the condition.
*/
addCriterion(criterionConfiguration) {
let criterion;
let criterionConfigurationWithId = this.generateCriterion(criterionConfiguration || null);
if (
criterionConfiguration.telemetry &&
(criterionConfiguration.telemetry === 'any' || criterionConfiguration.telemetry === 'all')
) {
criterion = new AllTelemetryCriterion(criterionConfigurationWithId, this.openmct);
} else {
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
}
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj));
criterion.on('telemetryStaleness', () => this.handleTelemetryStaleness());
if (!this.criteria) {
this.criteria = [];
}
this.criteria.push(criterion);
return criterionConfigurationWithId.id;
}
findCriterion(id) {
let criterion;
for (let i = 0, ii = this.criteria.length; i < ii; i++) {
if (this.criteria[i].id === id) {
criterion = {
item: this.criteria[i],
index: i
};
}
}
return criterion;
}
updateCriterion(id, criterionConfiguration) {
let found = this.findCriterion(id);
if (found) {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', this.handleCriterionUpdated);
newCriterion.on('telemetryIsOld', this.handleOldTelemetryCriterion);
newCriterion.on('telemetryStaleness', this.handleTelemetryStaleness);
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', this.handleCriterionUpdated);
criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion);
newCriterion.off('telemetryStaleness', this.handleTelemetryStaleness);
this.criteria.splice(found.index, 1, newCriterion);
}
}
destroyCriterion(id) {
let found = this.findCriterion(id);
if (found) {
let criterion = found.item;
criterion.off('criterionUpdated', this.handleCriterionUpdated);
criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion);
criterion.off('telemetryStaleness', this.handleTelemetryStaleness);
criterion.destroy();
this.criteria.splice(found.index, 1);
return true;
}
return false;
}
handleCriterionUpdated(criterion) {
let found = this.findCriterion(criterion.id);
if (found) {
this.criteria[found.index] = criterion.data;
}
}
handleOldTelemetryCriterion(updatedCriterion) {
this.result = evaluateResults(
this.criteria.map((criterion) => criterion.result),
this.trigger
);
let latestTimestamp = {};
latestTimestamp = getLatestTimestamp(
latestTimestamp,
updatedCriterion.data,
this.timeSystems,
this.openmct.time.timeSystem()
);
this.conditionManager.updateCurrentCondition(latestTimestamp);
}
handleTelemetryStaleness() {
this.result = evaluateResults(
this.criteria.map((criterion) => criterion.result),
this.trigger
);
this.conditionManager.updateCurrentCondition();
}
updateDescription() {
const triggerDescription = this.getTriggerDescription();
let description = '';
this.criteria.forEach((criterion, index) => {
if (!index) {
description = `Match if ${triggerDescription.prefix}`;
}
description = `${description} ${criterion.getDescription()} ${
index < this.criteria.length - 1 ? triggerDescription.conjunction : ''
}`;
});
this.summary = description;
}
getTriggerDescription() {
if (this.trigger) {
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
} else {
return {
conjunction: '',
prefix: ''
};
}
}
requestLADConditionResult(options) {
let latestTimestamp;
let criteriaResults = {};
const criteriaRequests = this.criteria.map((criterion) =>
criterion.requestLAD(this.conditionManager.telemetryObjects, options)
);
return Promise.all(criteriaRequests).then((results) => {
results.forEach((resultObj) => {
const {
id,
data,
data: { result }
} = resultObj;
if (this.findCriterion(id)) {
criteriaResults[id] = Boolean(result);
}
latestTimestamp = getLatestTimestamp(
latestTimestamp,
data,
this.timeSystems,
this.openmct.time.timeSystem()
);
});
return {
id: this.id,
data: Object.assign({}, latestTimestamp, {
result: evaluateResults(Object.values(criteriaResults), this.trigger)
})
};
});
}
getCriteria() {
return this.criteria;
}
destroyCriteria() {
let success = true;
//looping through the array backwards since destroyCriterion modifies the criteria array
for (let i = this.criteria.length - 1; i >= 0; i--) {
success = success && this.destroyCriterion(this.criteria[i].id);
}
return success;
}
destroy() {
this.destroyCriteria();
}
}