openmct/src/plugins/condition/Condition.js

346 lines
11 KiB
JavaScript

/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, 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 'eventemitter3';
import { v4 as uuid } from 'uuid';
import AllTelemetryCriterion from './criterion/AllTelemetryCriterion.js';
import TelemetryCriterion from './criterion/TelemetryCriterion.js';
import { TRIGGER_CONJUNCTION, TRIGGER_LABEL } from './utils/constants.js';
import { evaluateResults } from './utils/evaluator.js';
import { getLatestTimestamp } from './utils/time.js';
/*
* 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(latestDataTable) {
if (!latestDataTable) {
console.log('no data received');
return;
}
const hasNoTelemetry = this.hasNoTelemetry();
// if all the criteria in this condition have no telemetry, we want to force the condition result to evaluate
//if (this.hasNoTelemetry() || this.isTelemetryUsed(latestDataTable.id)) {
this.criteria.forEach((criterion) => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.updateResult(latestDataTable, this.conditionManager.telemetryObjects);
} else {
const relevantDatum = latestDataTable.get(criterion.telemetryObjectIdAsString);
if (relevantDatum !== undefined){
criterion.updateResult(relevantDatum);
}
}
});
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.getTimeSystem()
);
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.getTimeSystem()
);
});
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();
}
}