Merge pull request #2871 from nasa/dave/conditionals-own-results

[Conditionals] Request and subscription results are mutually exclusive
This commit is contained in:
David Tsay 2020-04-08 14:21:31 -07:00 committed by GitHub
commit 5d74882646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 67 deletions

View File

@ -25,6 +25,7 @@ import uuid from 'uuid';
import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { TRIGGER } from "./utils/constants";
import {computeCondition, computeConditionByLimit} from "./utils/evaluator";
import { getLatestTimestamp } from './utils/time';
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
/*
@ -59,7 +60,7 @@ export default class ConditionClass extends EventEmitter {
this.criteriaResults = {};
this.result = undefined;
this.latestTimestamp = {};
this.timeSystems = this.openmct.time.getAllTimeSystems();
if (conditionConfiguration.configuration.criteria) {
this.createCriteria(conditionConfiguration.configuration.criteria);
}
@ -169,9 +170,7 @@ export default class ConditionClass extends EventEmitter {
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
this.criteria.splice(found.index, 1, newCriterion);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
delete this.criteriaResults[criterion.id];
}
}
@ -185,15 +184,13 @@ export default class ConditionClass extends EventEmitter {
let found = this.findCriterion(id);
if (found) {
let criterion = found.item;
criterion.destroy();
// TODO this is passing the wrong args
criterion.off('criterionUpdated', (result) => {
this.handleCriterionUpdated(id, result);
criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj);
});
criterion.destroy();
this.criteria.splice(found.index, 1);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
delete this.criteriaResults[criterion.id];
return true;
}
return false;
@ -215,7 +212,6 @@ export default class ConditionClass extends EventEmitter {
const id = eventData.id;
if (this.findCriterion(id)) {
// The !! here is important to convert undefined to false otherwise the criteriaResults won't get deleted when the criteria is destroyed
this.criteriaResults[id] = !!eventData.data.result;
}
}
@ -226,20 +222,27 @@ export default class ConditionClass extends EventEmitter {
}
requestLADConditionResult() {
const criteriaResults = this.criteria
let latestTimestamp;
let criteriaResults = {};
const criteriaRequests = this.criteria
.map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects}));
return Promise.all(criteriaResults)
return Promise.all(criteriaRequests)
.then(results => {
results.forEach(result => {
this.updateCriteriaResults(result);
this.latestTimestamp = this.getLatestTimestamp(this.latestTimestamp, result.data)
results.forEach(resultObj => {
const { id, data, data: { result } } = resultObj;
if (this.findCriterion(id)) {
criteriaResults[id] = !!result;
}
latestTimestamp = getLatestTimestamp(
latestTimestamp,
data,
this.timeSystems
);
});
this.evaluate();
return {
id: this.id,
data: Object.assign({}, this.latestTimestamp, { result: this.result })
data: Object.assign({}, latestTimestamp, { result: this.evaluate(criteriaResults) })
}
});
}
@ -250,7 +253,7 @@ export default class ConditionClass extends EventEmitter {
handleConditionUpdated(datum) {
// trigger an updated event so that consumers can react accordingly
this.evaluate();
this.result = this.evaluate(this.criteriaResults);
this.emitEvent('conditionResultUpdated',
Object.assign({}, datum, { result: this.result })
);
@ -269,29 +272,16 @@ export default class ConditionClass extends EventEmitter {
return success;
}
evaluate() {
evaluate(results) {
if (this.trigger && this.trigger === TRIGGER.XOR) {
this.result = computeConditionByLimit(this.criteriaResults, 1);
return computeConditionByLimit(results, 1);
} else if (this.trigger && this.trigger === TRIGGER.NOT) {
this.result = computeConditionByLimit(this.criteriaResults, 0);
return computeConditionByLimit(results, 0);
} else {
this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
return computeCondition(results, this.trigger === TRIGGER.ALL);
}
}
getLatestTimestamp(current, compare) {
const timestamp = Object.assign({}, current);
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
if (!timestamp[timeSystem.key]
|| compare[timeSystem.key] > timestamp[timeSystem.key]
) {
timestamp[timeSystem.key] = compare[timeSystem.key];
}
});
return timestamp;
}
emitEvent(eventName, data) {
this.emit(eventName, {
id: this.id,

View File

@ -21,6 +21,7 @@
*****************************************************************************/
import Condition from "./Condition";
import { getLatestTimestamp } from './utils/time';
import uuid from "uuid";
import EventEmitter from 'EventEmitter';
@ -30,7 +31,7 @@ export default class ConditionManager extends EventEmitter {
this.openmct = openmct;
this.conditionSetDomainObject = conditionSetDomainObject;
this.timeAPI = this.openmct.time;
this.latestTimestamp = {};
this.timeSystems = this.openmct.time.getAllTimeSystems();
this.composition = this.openmct.composition.get(conditionSetDomainObject);
this.composition.on('add', this.subscribeToTelemetry, this);
this.composition.on('remove', this.unsubscribeFromTelemetry, this);
@ -163,9 +164,7 @@ export default class ConditionManager extends EventEmitter {
condition.off('conditionResultUpdated', this.handleConditionResult.bind(this));
this.conditionClassCollection.splice(index, 1);
this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
if (this.conditionResults[condition.id] !== undefined) {
delete this.conditionResults[condition.id];
}
delete this.conditionResults[condition.id];
this.persistConditions();
this.handleConditionResult();
}
@ -174,7 +173,6 @@ export default class ConditionManager extends EventEmitter {
return this.conditionClassCollection.find(conditionClass => conditionClass.id === id);
}
//this.$set(this.conditionClassCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
reorderConditions(reorderPlan) {
let oldConditions = Array.from(this.conditionSetDomainObject.configuration.conditionCollection);
let newCollection = [];
@ -186,12 +184,12 @@ export default class ConditionManager extends EventEmitter {
this.persistConditions();
}
getCurrentCondition() {
getCurrentCondition(conditionResults) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length-1];
for (let i = 0; i < conditionCollection.length - 1; i++) {
if (this.conditionResults[conditionCollection[i].id]) {
if (conditionResults[conditionCollection[i].id]) {
//first condition to be true wins
currentCondition = conditionCollection[i];
break;
@ -210,14 +208,14 @@ export default class ConditionManager extends EventEmitter {
if (this.findConditionById(id)) {
this.conditionResults[id] = resultObj.data.result;
}
this.updateTimestamp(resultObj.data);
}
handleConditionResult(resultObj) {
// update conditions results and then calculate the current condition
this.updateConditionResults(resultObj);
const currentCondition = this.getCurrentCondition();
const currentCondition = this.getCurrentCondition(this.conditionResults);
const timestamp = JSON.parse(JSON.stringify(resultObj.data))
delete timestamp.result
this.emit('conditionSetResultUpdated',
Object.assign(
{
@ -225,34 +223,36 @@ export default class ConditionManager extends EventEmitter {
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
this.latestTimestamp
timestamp
)
)
}
updateTimestamp(timestamp) {
this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
if (!this.latestTimestamp[timeSystem.key]
|| timestamp[timeSystem.key] > this.latestTimestamp[timeSystem.key]
) {
this.latestTimestamp[timeSystem.key] = timestamp[timeSystem.key];
}
});
}
requestLADConditionSetOutput() {
if (!this.conditionClassCollection.length) {
return Promise.resolve([]);
}
return this.compositionLoad.then(() => {
const ladConditionResults = this.conditionClassCollection
let latestTimestamp;
let conditionResults = {};
const conditionRequests = this.conditionClassCollection
.map(condition => condition.requestLADConditionResult());
return Promise.all(ladConditionResults)
return Promise.all(conditionRequests)
.then((results) => {
results.forEach(resultObj => { this.updateConditionResults(resultObj); });
const currentCondition = this.getCurrentCondition();
results.forEach(resultObj => {
const { id, data, data: { result } } = resultObj;
if (this.findConditionById(id)) {
conditionResults[id] = !!result;
}
latestTimestamp = getLatestTimestamp(
latestTimestamp,
data,
this.timeSystems
);
});
const currentCondition = this.getCurrentCondition(conditionResults);
return Object.assign(
{
@ -260,7 +260,7 @@ export default class ConditionManager extends EventEmitter {
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
this.latestTimestamp
latestTimestamp
);
});
});
@ -303,7 +303,7 @@ export default class ConditionManager extends EventEmitter {
this.composition.off('add', this.subscribeToTelemetry, this);
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
Object.values(this.subscriptions).forEach(unsubscribe => unsubscribe());
this.subscriptions = undefined;
delete this.subscriptions;
if(this.stopObservingForChanges) {
this.stopObservingForChanges();

View File

@ -49,6 +49,7 @@ describe('ConditionManager', () => {
};
let mockComposition;
let loader;
let mockTimeSystems;
function mockAngularComponents() {
let mockInjector = jasmine.createSpyObj('$injector', ['get']);
@ -111,6 +112,12 @@ describe('ConditionManager', () => {
openmct.objects.observe.and.returnValue(function () {});
openmct.objects.mutate.and.returnValue(function () {});
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);

View File

@ -30,7 +30,8 @@ let openmct = {},
testTelemetryObject,
conditionObj,
conditionManager,
mockBroadcastTelemetry;
mockBroadcastTelemetry,
mockTimeSystems;
describe("The condition", function () {
@ -74,6 +75,12 @@ describe("The condition", function () {
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
testConditionDefinition = {
id: '123-456',
configuration: {

View File

@ -0,0 +1,34 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export const getLatestTimestamp = (current, compare, timeSystems) => {
const timestamp = Object.assign({}, current);
timeSystems.forEach(timeSystem => {
if (!timestamp[timeSystem.key]
|| compare[timeSystem.key] > timestamp[timeSystem.key]
) {
timestamp[timeSystem.key] = compare[timeSystem.key];
}
});
return timestamp;
}