diff --git a/package.json b/package.json index 50a8d53cd9..bd876eb67e 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "request": "^2.69.0", "split": "^1.0.0", "style-loader": "^1.0.1", + "uuid": "^3.3.3", "v8-compile-cache": "^1.1.0", "vue": "2.5.6", "vue-loader": "^15.2.6", diff --git a/src/plugins/condition/Condition.js b/src/plugins/condition/Condition.js index 94f4b98c68..5a5bb3e790 100644 --- a/src/plugins/condition/Condition.js +++ b/src/plugins/condition/Condition.js @@ -21,9 +21,8 @@ *****************************************************************************/ import * as EventEmitter from 'eventemitter3'; -import UUID from 'uuid'; +import uuid from 'uuid'; import TelemetryCriterion from "@/plugins/condition/criterion/TelemetryCriterion"; -import {computeConditionForAll, computeConditionForAny} from 'utils/evaluator' import { TRIGGER } from "@/plugins/condition/utils/constants"; /* @@ -31,12 +30,10 @@ import { TRIGGER } from "@/plugins/condition/utils/constants"; * trigger: 'any'/'all', * criteria: [ * { -* object: { -* operator: '', -* input: '', -* metaDataKey: '', -* telemetryObjectKey: 'someTelemetryObjectKey' -* } +* operation: '', +* input: '', +* metaDataKey: '', +* key: 'someTelemetryObjectKey' * } * ] * } @@ -47,8 +44,12 @@ export default class Condition extends EventEmitter { super(); this.openmct = openmct; - this.id = new UUID(); - this.criteriaMap = conditionDefinition.criteria ? this.createCriteria(conditionDefinition.criteria) : {}; + this.id = uuid(); + if (conditionDefinition.criteria) { + this.createCriteria(conditionDefinition.criteria); + } else { + this.criteria = []; + } this.trigger = conditionDefinition.trigger; this.result = null; } @@ -60,13 +61,13 @@ export default class Condition extends EventEmitter { } } - generateNewCriterion() { + generateCriterion(criterionDefinition) { return { - id: new UUID(), - object: '', - key: '', - operation: '', - values: [] + id: uuid(), + operation: criterionDefinition.operation || '', + input: criterionDefinition.input === undefined ? [] : criterionDefinition.input, + metaDataKey: criterionDefinition.metaDataKey || '', + key: criterionDefinition.key || '' }; } @@ -85,23 +86,43 @@ export default class Condition extends EventEmitter { * adds criterion to the condition. */ addCriterion(criterionDefinition) { - if (!criterionDefinition) { - criterionDefinition = this.generateNewCriterion(); + let criterionDefinitionWithId = this.generateCriterion(criterionDefinition || null); + let criterion = new TelemetryCriterion(criterionDefinitionWithId, this.openmct); + criterion.on('criterionUpdated', this.handleCriterionUpdated); + if (!this.criteria) { + this.criteria = []; } - let criterion = new TelemetryCriterion(criterionDefinition.object, this.openmct); - criterion.on('criterion::Update', this.handleCriterionUpdated); - this.criteriaMap[criterionDefinition.id] = criterion; + this.criteria.push(criterion); this.handleConditionUpdated(); - return criterionDefinition.id; + return criterionDefinitionWithId.id; } findCriterion(id) { - return this.criteriaMap[id] || null; + 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, criterionDefinition) { - if (this.destroyCriterion(id)) { - this.criteriaMap[id] = new TelemetryCriterion(criterionDefinition.object, this.openmct); + let found = this.findCriterion(id); + if (found) { + const newCriterionDefinition = this.generateCriterion(criterionDefinition); + let newCriterion = new TelemetryCriterion(newCriterionDefinition, this.openmct); + let criterion = found.item; + criterion.unsubscribe(); + criterion.off('criterionUpdated', (result) => { + this.handleCriterionUpdated(id, result); + }); + this.criteria.splice(found.index, 1, newCriterion); this.handleConditionUpdated(); } } @@ -113,14 +134,14 @@ export default class Condition extends EventEmitter { } destroyCriterion(id) { - let criterion = this.findCriterion(id); - const criterionId = id; - if (criterion) { + let found = this.findCriterion(id); + if (found) { + let criterion = found.item; criterion.unsubscribe(); - criterion.off('criterion::Update', (result) => { - this.handleCriterionUpdated(criterionId, result); + criterion.off('criterionUpdated', (result) => { + this.handleCriterionUpdated(id, result); }); - delete this.criteriaMap[id]; + this.criteria.splice(found.index, 1); return true; } return false; @@ -135,41 +156,33 @@ export default class Condition extends EventEmitter { handleConditionUpdated() { // trigger an updated event so that consumers can react accordingly + this.emitResult(); } getCriteria() { - let criteria = []; - for(let id in this.criteriaMap) { - if (this.criteriaMap.hasOwnProperty(id) && this.criteriaMap[id]) { - criteria.push(this.criteriaMap[id]); - } - } - - return criteria; + return this.criteria; } destroyCriteria() { let success = true; - for(let id in this.criteriaMap) { - if (this.criteriaMap.hasOwnProperty(id) && this.criteriaMap[id]) { - success = success && this.destroyCriterion(this.criteriaMap[id]); - } + //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; } + //TODO: implement as part of the evaluator class task. evaluate() { - let criteria = this.getCriteria(); if (this.trigger === TRIGGER.ANY) { - this.result = computeConditionForAny(criteria); + this.result = false; } else if (this.trigger === TRIGGER.ALL) { - this.result = computeConditionForAll(criteria); + this.result = false; } } emitResult(data, error) { - this.emit('condition::Update', { + this.emit('conditionUpdated', { identifier: this.id, data: data, error: error diff --git a/src/plugins/condition/ConditionSpec.js b/src/plugins/condition/ConditionSpec.js new file mode 100644 index 0000000000..efb19d6fbe --- /dev/null +++ b/src/plugins/condition/ConditionSpec.js @@ -0,0 +1,111 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2019, 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 Condition from "./Condition"; +import {TRIGGER} from "./utils/constants"; +import TelemetryCriterion from "./criterion/TelemetryCriterion"; + +let openmct = {}, + mockListener, + testConditionDefinition, + testTelemetryObject, + conditionObj; + +describe("The condition", function () { + + beforeEach (() => { + mockListener = jasmine.createSpy('listener'); + testTelemetryObject = { + identifier:{ namespace: "", key: "test-object"}, + type: "test-object", + name: "Test Object", + telemetry: { + values: [{ + key: "some-key", + name: "Some attribute", + hints: { + domain: 1 + } + }, { + key: "some-other-key", + name: "Another attribute", + hints: { + range: 1 + } + }] + } + }; + openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']); + openmct.objects.get.and.returnValue(testTelemetryObject); + openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key); + openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']); + openmct.telemetry.isTelemetryObject.and.returnValue(true); + + testConditionDefinition = { + trigger: TRIGGER.ANY, + criteria: [ + { + operation: 'equalTo', + input: false, + metaDataKey: 'value', + key: testTelemetryObject.identifier + } + ] + }; + + conditionObj = new Condition( + testConditionDefinition, + openmct + ); + + conditionObj.on('conditionUpdated', mockListener); + + }); + + it("generates criteria with an id", function () { + const testCriterion = testConditionDefinition.criteria[0]; + let criterion = conditionObj.generateCriterion(testCriterion); + expect(criterion.id).toBeDefined(); + expect(criterion.operation).toEqual(testCriterion.operation); + expect(criterion.input).toEqual(testCriterion.input); + expect(criterion.metaDataKey).toEqual(testCriterion.metaDataKey); + expect(criterion.key).toEqual(testCriterion.key); + }); + + it("initializes with an id", function () { + expect(conditionObj.id).toBeDefined(); + }); + + it("initializes with criteria from the condition definition", function () { + expect(conditionObj.criteria.length).toEqual(1); + let criterion = conditionObj.criteria[0]; + expect(criterion instanceof TelemetryCriterion).toBeTrue(); + expect(criterion.operator).toEqual(testConditionDefinition.operator); + expect(criterion.input).toEqual(testConditionDefinition.input); + expect(criterion.metaDataKey).toEqual(testConditionDefinition.metaDataKey); + expect(criterion.key).toEqual(testConditionDefinition.key); + }); + + it("initializes with the trigger from the condition definition", function () { + expect(conditionObj.trigger).toEqual(testConditionDefinition.trigger); + }); +});