openmct/src/plugins/condition/Condition.js

249 lines
8.5 KiB
JavaScript
Raw Normal View History

/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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 * as EventEmitter from 'eventemitter3';
2020-01-10 10:43:55 -08:00
import uuid from 'uuid';
import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { TRIGGER } from "./utils/constants";
import {computeCondition} from "./utils/evaluator";
/*
* conditionConfiguration = {
* identifier: {
* key: '',
* namespace: ''
* },
* trigger: 'any'/'all',
* criteria: [
* {
* telemetry: '',
2020-01-10 10:43:55 -08:00
* operation: '',
* input: [],
* metadata: ''
* }
* ]
* }
*/
export default class ConditionClass extends EventEmitter {
/**
* Manages criteria and emits the result of - true or false - based on criteria evaluated.
* @constructor
* @param conditionConfiguration: {identifier: {domainObject.identifier},trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} }
* @param openmct
*/
constructor(conditionConfiguration, openmct) {
super();
this.openmct = openmct;
this.id = this.openmct.objects.makeKeyString(conditionConfiguration.identifier);
this.criteria = [];
this.criteriaResults = {};
2020-02-28 11:15:19 -08:00
this.result = undefined;
if (conditionConfiguration.configuration.criteria) {
this.createCriteria(conditionConfiguration.configuration.criteria);
2020-01-10 10:43:55 -08:00
}
this.trigger = conditionConfiguration.configuration.trigger;
this.openmct.objects.get(this.id).then(obj => this.observeForChanges(obj));
}
observeForChanges(conditionDO) {
this.stopObservingForChanges = this.openmct.objects.observe(conditionDO, '*', this.update.bind(this));
}
update(newDomainObject) {
this.updateTrigger(newDomainObject.configuration.trigger);
2020-02-18 16:04:14 -08:00
this.updateCriteria(newDomainObject.configuration.criteria);
}
updateTrigger(trigger) {
if (this.trigger !== trigger) {
this.trigger = trigger;
this.handleConditionUpdated();
}
}
generateCriterion(criterionConfiguration) {
return {
2020-01-10 10:43:55 -08:00
id: uuid(),
2020-02-13 16:13:29 -08:00
telemetry: criterionConfiguration.telemetry || '',
operation: criterionConfiguration.operation || '',
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
metadata: criterionConfiguration.metadata || ''
};
}
createCriteria(criterionConfigurations) {
2020-02-13 16:13:29 -08:00
criterionConfigurations.forEach((criterionConfiguration) => {
this.addCriterion(criterionConfiguration);
});
}
updateCriteria(criterionConfigurations) {
this.destroyCriteria();
this.createCriteria(criterionConfigurations);
}
/**
* adds criterion to the condition.
*/
addCriterion(criterionConfiguration) {
let criterionConfigurationWithId = this.generateCriterion(criterionConfiguration || null);
let criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
2020-01-10 10:43:55 -08:00
if (!this.criteria) {
this.criteria = [];
}
2020-01-10 10:43:55 -08:00
this.criteria.push(criterion);
return criterionConfigurationWithId.id;
}
findCriterion(id) {
2020-01-10 10:43:55 -08:00
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) {
2020-01-10 10:43:55 -08:00
let found = this.findCriterion(id);
if (found) {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
2020-01-10 10:43:55 -08:00
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
2020-01-10 10:43:55 -08:00
this.criteria.splice(found.index, 1, newCriterion);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
}
}
removeCriterion(id) {
if (this.destroyCriterion(id)) {
this.handleConditionUpdated();
}
}
destroyCriterion(id) {
2020-01-10 10:43:55 -08:00
let found = this.findCriterion(id);
if (found) {
let criterion = found.item;
criterion.destroy();
2020-02-28 11:15:19 -08:00
// TODO this is passing the wrong args
2020-01-10 10:43:55 -08:00
criterion.off('criterionUpdated', (result) => {
this.handleCriterionUpdated(id, result);
});
2020-01-10 10:43:55 -08:00
this.criteria.splice(found.index, 1);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
return true;
}
return false;
}
handleCriterionUpdated(criterion) {
let found = this.findCriterion(criterion.id);
if (found) {
this.criteria[found.index] = criterion.data;
2020-02-13 16:13:29 -08:00
this.subscribe();
2020-02-28 11:15:19 -08:00
// TODO nothing is listening to this
this.emitEvent('conditionUpdated', {
trigger: this.trigger,
criteria: this.criteria
});
}
}
handleCriterionResult(eventData) {
2020-02-28 14:35:57 -08:00
const id = eventData.id;
const conditionData = eventData.data;
2020-03-03 11:34:30 -08:00
2020-02-28 11:15:19 -08:00
if (this.findCriterion(id)) {
this.criteriaResults[id] = eventData.data.result;
}
2020-02-28 14:35:57 -08:00
2020-03-03 11:34:30 -08:00
this.handleConditionUpdated();
// conditionData.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
// this.emitEvent('conditionResultUpdated', conditionData);
}
subscribe() {
2020-02-28 11:15:19 -08:00
// TODO it looks like on any single criterion update subscriptions fire for all criteria
this.criteria.forEach((criterion) => {
criterion.subscribe();
})
}
2020-03-03 11:34:30 -08:00
handleConditionUpdated() {
// trigger an updated event so that consumers can react accordingly
this.evaluate();
this.emitEvent('conditionResultUpdated', {result: this.result});
}
getCriteria() {
2020-01-10 10:43:55 -08:00
return this.criteria;
}
destroyCriteria() {
let success = true;
2020-01-10 10:43:55 -08:00
//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;
}
2020-03-03 11:34:30 -08:00
evaluate() {
this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
}
emitEvent(eventName, data) {
this.emit(eventName, {
id: this.id,
data: data
});
}
destroy() {
if (typeof this.stopObservingForChanges === 'function') {
this.stopObservingForChanges();
}
this.destroyCriteria();
}
}