addressed review comments

This commit is contained in:
Joel McKinnon 2020-02-18 12:40:33 -08:00
commit f08caa6135
15 changed files with 387 additions and 213 deletions

View File

@ -24,6 +24,7 @@ import * as EventEmitter from 'eventemitter3';
import uuid from 'uuid'; import uuid from 'uuid';
import TelemetryCriterion from "@/plugins/condition/criterion/TelemetryCriterion"; import TelemetryCriterion from "@/plugins/condition/criterion/TelemetryCriterion";
import { TRIGGER } from "@/plugins/condition/utils/constants"; import { TRIGGER } from "@/plugins/condition/utils/constants";
import {computeCondition} from "@/plugins/condition/utils/evaluator";
/* /*
* conditionDefinition = { * conditionDefinition = {
@ -56,6 +57,7 @@ export default class ConditionClass extends EventEmitter {
this.openmct = openmct; this.openmct = openmct;
this.id = this.openmct.objects.makeKeyString(conditionDefinition.identifier); this.id = this.openmct.objects.makeKeyString(conditionDefinition.identifier);
this.criteria = []; this.criteria = [];
this.criteriaResults = {};
if (conditionDefinition.definition.criteria) { if (conditionDefinition.definition.criteria) {
this.createCriteria(conditionDefinition.definition.criteria); this.createCriteria(conditionDefinition.definition.criteria);
} }
@ -71,12 +73,11 @@ export default class ConditionClass extends EventEmitter {
update(newDomainObject) { update(newDomainObject) {
this.updateTrigger(newDomainObject.definition.trigger); this.updateTrigger(newDomainObject.definition.trigger);
this.updateCriteria(newDomainObject.definition.criteria); this.updateCriteria(newDomainObject.definition.criteria);
this.handleConditionUpdated();
} }
updateTrigger(conditionDefinition) { updateTrigger(trigger) {
if (this.trigger !== conditionDefinition.trigger) { if (this.trigger !== trigger) {
this.trigger = conditionDefinition.trigger; this.trigger = trigger;
this.handleConditionUpdated(); this.handleConditionUpdated();
} }
} }
@ -109,6 +110,7 @@ export default class ConditionClass extends EventEmitter {
let criterionDefinitionWithId = this.generateCriterion(criterionDefinition || null); let criterionDefinitionWithId = this.generateCriterion(criterionDefinition || null);
let criterion = new TelemetryCriterion(criterionDefinitionWithId, this.openmct); let criterion = new TelemetryCriterion(criterionDefinitionWithId, this.openmct);
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
if (!this.criteria) { if (!this.criteria) {
this.criteria = []; this.criteria = [];
} }
@ -138,12 +140,17 @@ export default class ConditionClass extends EventEmitter {
if (found) { if (found) {
const newCriterionDefinition = this.generateCriterion(criterionDefinition); const newCriterionDefinition = this.generateCriterion(criterionDefinition);
let newCriterion = new TelemetryCriterion(newCriterionDefinition, this.openmct); let newCriterion = new TelemetryCriterion(newCriterionDefinition, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
let criterion = found.item; let criterion = found.item;
criterion.unsubscribe(); criterion.unsubscribe();
criterion.off('criterionUpdated', (result) => { criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
this.handleCriterionUpdated(id, result); criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
});
this.criteria.splice(found.index, 1, newCriterion); this.criteria.splice(found.index, 1, newCriterion);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
this.handleConditionUpdated(); this.handleConditionUpdated();
} }
} }
@ -163,6 +170,9 @@ export default class ConditionClass extends EventEmitter {
this.handleCriterionUpdated(id, result); this.handleCriterionUpdated(id, result);
}); });
this.criteria.splice(found.index, 1); this.criteria.splice(found.index, 1);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
return true; return true;
} }
return false; return false;
@ -173,14 +183,30 @@ export default class ConditionClass extends EventEmitter {
if (found) { if (found) {
this.criteria[found.index] = criterion.data; this.criteria[found.index] = criterion.data;
//Most likely don't need this. //Most likely don't need this.
this.subscribe();
this.emitEvent('conditionUpdated', { this.emitEvent('conditionUpdated', {
trigger: this.trigger, trigger: this.trigger,
criteria: this.criteria criteria: this.criteria
}); });
} }
}
handleCriterionResult(eventData) {
let id = eventData.id;
let result = eventData.data.result;
let found = this.findCriterion(id);
if (found) {
this.criteriaResults[id] = result;
}
this.handleConditionUpdated(); this.handleConditionUpdated();
} }
subscribe() {
this.criteria.forEach((criterion) => {
criterion.subscribe();
})
}
handleConditionUpdated() { handleConditionUpdated() {
// trigger an updated event so that consumers can react accordingly // trigger an updated event so that consumers can react accordingly
this.evaluate(); this.evaluate();
@ -202,11 +228,7 @@ export default class ConditionClass extends EventEmitter {
//TODO: implement as part of the evaluator class task. //TODO: implement as part of the evaluator class task.
evaluate() { evaluate() {
if (this.trigger === TRIGGER.ANY) { this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
this.result = true;
} else if (this.trigger === TRIGGER.ALL) {
this.result = false;
}
} }
emitEvent(eventName, data) { emitEvent(eventName, data) {

View File

@ -58,7 +58,7 @@ export default class ConditionSetViewProvider {
isEditing isEditing
} }
}, },
template: '<condition-set ref="conditionSet" :isEditing="isEditing"></condition-set>' template: '<condition-set :isEditing="isEditing"></condition-set>'
}); });
}, },
onEditModeChange: (isEditing) => { onEditModeChange: (isEditing) => {

View File

@ -64,15 +64,17 @@ describe("The condition", function () {
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values); openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
testConditionDefinition = { testConditionDefinition = {
trigger: TRIGGER.ANY, definition: {
criteria: [ trigger: TRIGGER.ANY,
{ criteria: [
operation: 'equalTo', {
input: false, operation: 'equalTo',
metaDataKey: 'value', input: false,
key: testTelemetryObject.identifier metaDataKey: 'value',
} key: testTelemetryObject.identifier
] }
]
}
}; };
conditionObj = new Condition( conditionObj = new Condition(
@ -85,7 +87,7 @@ describe("The condition", function () {
}); });
it("generates criteria with an id", function () { it("generates criteria with an id", function () {
const testCriterion = testConditionDefinition.criteria[0]; const testCriterion = testConditionDefinition.definition.criteria[0];
let criterion = conditionObj.generateCriterion(testCriterion); let criterion = conditionObj.generateCriterion(testCriterion);
expect(criterion.id).toBeDefined(); expect(criterion.id).toBeDefined();
expect(criterion.operation).toEqual(testCriterion.operation); expect(criterion.operation).toEqual(testCriterion.operation);
@ -102,14 +104,13 @@ describe("The condition", function () {
expect(conditionObj.criteria.length).toEqual(1); expect(conditionObj.criteria.length).toEqual(1);
let criterion = conditionObj.criteria[0]; let criterion = conditionObj.criteria[0];
expect(criterion instanceof TelemetryCriterion).toBeTrue(); expect(criterion instanceof TelemetryCriterion).toBeTrue();
expect(criterion.operator).toEqual(testConditionDefinition.operator); expect(criterion.operator).toEqual(testConditionDefinition.definition.criteria[0].operator);
expect(criterion.input).toEqual(testConditionDefinition.input); expect(criterion.input).toEqual(testConditionDefinition.definition.criteria[0].input);
expect(criterion.metaDataKey).toEqual(testConditionDefinition.metaDataKey); expect(criterion.metaDataKey).toEqual(testConditionDefinition.definition.criteria[0].metaDataKey);
expect(criterion.key).toEqual(testConditionDefinition.key);
}); });
it("initializes with the trigger from the condition definition", function () { it("initializes with the trigger from the condition definition", function () {
expect(conditionObj.trigger).toEqual(testConditionDefinition.trigger); expect(conditionObj.trigger).toEqual(testConditionDefinition.definition.trigger);
}); });
it("destroys all criteria for a condition", function () { it("destroys all criteria for a condition", function () {

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- TODO: current condition class should be set using openmct.objects.makeKeyString(<identifier>) -->
<div v-if="condition" <div v-if="condition"
id="conditionArea" id="conditionArea"
class="c-cs-ui__conditions" class="c-cs-ui__conditions"
@ -21,6 +22,8 @@
</template> </template>
<script> <script>
import ConditionClass from "@/plugins/condition/Condition";
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
@ -38,10 +41,26 @@ export default {
condition: this.condition condition: this.condition
}; };
}, },
destroyed() {
this.conditionClass.off('conditionResultUpdated', this.handleConditionResult.bind(this));
if (this.conditionClass && typeof this.conditionClass.destroy === 'function') {
this.conditionClass.destroy();
}
},
mounted() { mounted() {
this.openmct.objects.get(this.conditionIdentifier).then((obj => { this.openmct.objects.get(this.conditionIdentifier).then((obj => {
this.condition = obj; this.condition = obj;
this.conditionClass = new ConditionClass(this.condition, this.openmct);
this.conditionClass.on('conditionResultUpdated', this.handleConditionResult.bind(this));
})); }));
},
methods: {
handleConditionResult(args) {
this.$emit('conditionResultUpdated', {
id: this.conditionIdentifier,
result: args.data.result
})
}
} }
} }
</script> </script>

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template> <template>
<section id="conditionCollection" <section id="conditionCollection"
class="c-cs__ui_section" class="c-cs__ui_section"
@ -41,6 +63,7 @@
@dragover.prevent @dragover.prevent
></div> ></div>
<ConditionEdit :condition-identifier="conditionIdentifier" <ConditionEdit :condition-identifier="conditionIdentifier"
:telemetry="telemetryObjs"
:current-condition-identifier="currentConditionIdentifier" :current-condition-identifier="currentConditionIdentifier"
:condition-index="index" :condition-index="index"
@updateCurrentCondition="updateCurrentCondition" @updateCurrentCondition="updateCurrentCondition"
@ -53,6 +76,7 @@
<div v-else> <div v-else>
<Condition :condition-identifier="conditionIdentifier" <Condition :condition-identifier="conditionIdentifier"
:current-condition-identifier="currentConditionIdentifier" :current-condition-identifier="currentConditionIdentifier"
@conditionResultUpdated="handleConditionResult"
/> />
</div> </div>
</li> </li>
@ -67,7 +91,6 @@ import Condition from '../../condition/components/Condition.vue';
import ConditionEdit from '../../condition/components/ConditionEdit.vue'; import ConditionEdit from '../../condition/components/ConditionEdit.vue';
import uuid from 'uuid'; import uuid from 'uuid';
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
components: { components: {
@ -84,7 +107,9 @@ export default {
conditionCollection: [], conditionCollection: [],
conditions: [], conditions: [],
currentConditionIdentifier: this.currentConditionIdentifier || {}, currentConditionIdentifier: this.currentConditionIdentifier || {},
moveIndex: null telemetryObjs: this.telemetryObjs,
moveIndex: Number,
isDragging: false
}; };
}, },
destroyed() { destroyed() {
@ -96,6 +121,7 @@ export default {
this.instantiate = this.openmct.$injector.get('instantiate'); this.instantiate = this.openmct.$injector.get('instantiate');
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addTelemetry); this.composition.on('add', this.addTelemetry);
this.composition.on('remove', this.removeTelemetry);
this.composition.load(); this.composition.load();
this.conditionCollection = this.domainObject.configuration ? this.domainObject.configuration.conditionCollection : []; this.conditionCollection = this.domainObject.configuration ? this.domainObject.configuration.conditionCollection : [];
if (!this.conditionCollection.length) { if (!this.conditionCollection.length) {
@ -107,10 +133,11 @@ export default {
methods: { methods: {
setMoveIndex(index) { setMoveIndex(index) {
this.moveIndex = index; this.moveIndex = index;
this.isDragging = true;
}, },
dropCondition(e) { dropCondition(e) {
let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target); let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
if (targetIndex > this.moveIndex) { targetIndex-- } if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
const oldIndexArr = Object.keys(this.conditionCollection); const oldIndexArr = Object.keys(this.conditionCollection);
const move = function (arr, old_index, new_index) { const move = function (arr, old_index, new_index) {
while (old_index < 0) { while (old_index < 0) {
@ -138,11 +165,12 @@ export default {
this.reorder(reorderPlan); this.reorder(reorderPlan);
e.target.classList.remove("dragging"); e.target.classList.remove("dragging");
this.isDragging = false;
}, },
dragEnter(e) { dragEnter(e) {
if (!this.isDragging) { return }
let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target); let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
if (targetIndex > this.moveIndex) { targetIndex-- }
if (this.moveIndex === targetIndex) { return } if (this.moveIndex === targetIndex) { return }
e.target.classList.add("dragging"); e.target.classList.add("dragging");
}, },
@ -164,11 +192,21 @@ export default {
break; break;
} }
} }
this.$emit('current-condition-updated', currentConditionIdentifier); this.$emit('currentConditionUpdated', currentConditionIdentifier);
}, },
addTelemetry(telemetryDomainObject) { addTelemetry(telemetryDomainObject) {
this.telemetryObjs.push(telemetryDomainObject); this.telemetryObjs.push(telemetryDomainObject);
}, },
removeTelemetry(telemetryDomainObjectIdentifier) {
let index = _.findIndex(this.telemetryObjs, (obj) => {
let objId = this.openmct.objects.makeKeyString(obj.identifier);
let id = this.openmct.objects.makeKeyString(telemetryDomainObjectIdentifier);
return objId === id;
});
if (index > -1) {
this.telemetryObjs.splice(index, 1);
}
},
/* /*
Adds a condition to list via programatic creation of default for initial list, manual Adds a condition to list via programatic creation of default for initial list, manual
creation via Add Condition button, or duplication via button in title bar of condition. creation via Add Condition button, or duplication via button in title bar of condition.
@ -180,13 +218,13 @@ export default {
index (number): index of condition being duplicated index (number): index of condition being duplicated
*/ */
addCondition(event, isDefault, isClone, definition, index) { addCondition(event, isDefault, isClone, definition, index) {
let conditionDO = this.getConditionDomainObject(!!isDefault, isClone, definition); let conditionDomainObject = this.getConditionDomainObject(!!isDefault, isClone, definition);
//persist the condition DO so that we can do an openmct.objects.get on it and only persist the identifier in the conditionCollection of conditionSet //persist the condition domain object so that we can do an openmct.objects.get on it and only persist the identifier in the conditionCollection of conditionSet
this.openmct.objects.mutate(conditionDO, 'created', new Date()); this.openmct.objects.mutate(conditionDomainObject, 'created', new Date());
if (!isClone) { if (!isClone) {
this.conditionCollection.unshift(conditionDO.identifier); this.conditionCollection.unshift(conditionDomainObject.identifier);
} else { } else {
this.conditionCollection.splice(index + 1, 0, conditionDO.identifier); this.conditionCollection.splice(index + 1, 0, conditionDomainObject.identifier);
} }
this.persist(); this.persist();
}, },
@ -195,7 +233,7 @@ export default {
}, },
getConditionDomainObject(isDefault, isClone, definition) { getConditionDomainObject(isDefault, isClone, definition) {
const definitionTemplate = { const definitionTemplate = {
name: isDefault ? 'Default' : 'Unnamed Condition', name: isDefault ? 'Default' : (isClone ? 'Copy of ' : '') + 'Unnamed Condition',
output: 'false', output: 'false',
trigger: 'any', trigger: 'any',
criteria: isDefault ? [] : [{ criteria: isDefault ? [] : [{
@ -204,7 +242,7 @@ export default {
metaDataKey: this.openmct.telemetry.getMetadata(this.telemetryObjs[0]).values()[0].key, metaDataKey: this.openmct.telemetry.getMetadata(this.telemetryObjs[0]).values()[0].key,
key: this.telemetryObjs.length ? this.openmct.objects.makeKeyString(this.telemetryObjs[0].identifier) : null key: this.telemetryObjs.length ? this.openmct.objects.makeKeyString(this.telemetryObjs[0].identifier) : null
}] }]
} };
let conditionObj = { let conditionObj = {
isDefault: isDefault, isDefault: isDefault,
identifier: { identifier: {
@ -214,10 +252,10 @@ export default {
definition: isClone ? definition: definitionTemplate, definition: isClone ? definition: definitionTemplate,
summary: 'summary description' summary: 'summary description'
}; };
let conditionDOKeyString = this.openmct.objects.makeKeyString(conditionObj.identifier); let conditionDomainObjectKeyString = this.openmct.objects.makeKeyString(conditionObj.identifier);
let newDO = this.instantiate(conditionObj, conditionDOKeyString); let newDomainObject = this.instantiate(conditionObj, conditionDomainObjectKeyString);
return newDO.useCapability('adapter'); return newDomainObject.useCapability('adapter');
}, },
removeCondition(identifier) { removeCondition(identifier) {
let index = _.findIndex(this.conditionCollection, (condition) => { let index = _.findIndex(this.conditionCollection, (condition) => {

View File

@ -1,16 +1,37 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template> <template>
<!-- TODO: current condition class should be set using openmct.objects.makeKeyString(<identifier>) -->
<div v-if="condition" <div v-if="condition"
:data-condition-index="conditionIndex"
class="c-c-editui__conditions c-c-container__container c-c__drag-wrapper" class="c-c-editui__conditions c-c-container__container c-c__drag-wrapper"
:class="['widget-condition', { 'widget-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }]" :class="['widget-condition', { 'widget-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragover.stop
> >
<div class="title-bar"> <div class="title-bar">
<span <span class="c-c__menu-hamburger"
class="c-c__menu-hamburger" :class="{ 'is-enabled': !condition.isDefault }"
:class="{ 'is-enabled': !condition.isDefault }" :draggable="!condition.isDefault"
@dragstart="dragStart"
@dragover.stop
></span> ></span>
<span <span
class="is-enabled flex-elem" class="is-enabled flex-elem"
@ -19,6 +40,7 @@
></span> ></span>
<div class="condition-summary"> <div class="condition-summary">
<span class="condition-name">{{ condition.definition.name }}</span> <span class="condition-name">{{ condition.definition.name }}</span>
<!-- TODO: description should be derived from criteria -->
<span class="condition-description">{{ condition.definition.name }}</span> <span class="condition-description">{{ condition.definition.name }}</span>
</div> </div>
<span v-if="!condition.isDefault" <span v-if="!condition.isDefault"
@ -86,17 +108,22 @@
</li> </li>
</ul> </ul>
<ul class="t-widget-condition-config"> <ul class="t-widget-condition-config">
<li v-if="telemetryObject && telemetryMetadata" <li v-if="telemetry.length"
class="has-local-controls t-condition" class="has-local-controls t-condition"
> >
<label>when</label> <label>when</label>
<span class="t-configuration"> <span class="t-configuration">
<span class="controls"> <span class="controls">
<select v-model="selectedTelemetryKey" <select v-model="selectedTelemetryKey"
class="" @change="updateTelemetryMetaData"
> >
<option value="">- Select Telemetry -</option> <option value="">- Select Telemetry -</option>
<option :value="telemetryObject.identifier">{{ telemetryObject.name }}</option> <option v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select> </select>
</span> </span>
<span class="controls"> <span class="controls">
@ -111,8 +138,8 @@
</select> </select>
</span> </span>
<span class="controls"> <span class="controls">
<select v-model="selectedOperationKey" <select v-model="selectOperationName"
@change="operationKeyChange" @change="setInputValueVisibility"
> >
<option value="">- Select Comparison -</option> <option value="">- Select Comparison -</option>
<option v-for="option in operations" <option v-for="option in operations"
@ -125,7 +152,7 @@
<input v-if="comparisonValueField" <input v-if="comparisonValueField"
class="t-condition-name-input" class="t-condition-name-input"
type="text" type="text"
@keyup="getOperationValue" v-model="operationValue"
> >
</span> </span>
</span> </span>
@ -155,6 +182,11 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
telemetry: {
type: Array,
required: true,
default: () => []
},
conditionIndex: { conditionIndex: {
type: Number, type: Number,
required: true required: true
@ -167,12 +199,13 @@ export default {
telemetryObject: this.telemetryObject, telemetryObject: this.telemetryObject,
telemetryMetadata: this.telemetryMetadata, telemetryMetadata: this.telemetryMetadata,
operations: OPERATIONS, operations: OPERATIONS,
selectedMetaDataKey: null, selectedMetaDataKey: '',
selectedTelemetryKey: '', selectedTelemetryKey: '',
selectedOperationKey: '', selectOperationName: '',
selectedOutputKey: null, selectedOutputKey: '',
stringOutputField: false, stringOutputField: false,
comparisonValueField: false, comparisonValueField: false,
operationValue: this.operationValue,
outputOptions: [ outputOptions: [
{ {
key: 'false', key: 'false',
@ -191,32 +224,53 @@ export default {
}; };
}, },
destroyed() { destroyed() {
this.conditionClass.off('conditionResultUpdated', this.handleConditionResult.bind(this)); this.destroy();
if (this.conditionClass && typeof this.conditionClass.destroy === 'function') {
this.conditionClass.destroy();
}
}, },
mounted() { mounted() {
this.openmct.objects.get(this.conditionIdentifier).then((obj => { this.openmct.objects.get(this.conditionIdentifier).then((obj => {
this.condition = obj; this.condition = obj;
this.initialize();
}));
},
updated() {
//validate telemetry exists, update criteria as needed
this.validate();
this.persist();
},
methods: {
dragStart(e) {
e.dataTransfer.effectAllowed = "copyMove";
e.dataTransfer.setDragImage(e.target.closest('.c-c-container__container'), 0, 0);
this.$emit('setMoveIndex', this.conditionIndex);
},
initialize() {
this.setOutput(); this.setOutput();
this.setOperation(); this.setOperation();
this.updateTelemetry(); this.updateTelemetry();
this.conditionClass = new ConditionClass(this.condition, this.openmct); this.conditionClass = new ConditionClass(this.condition, this.openmct);
this.conditionClass.on('conditionResultUpdated', this.handleConditionResult.bind(this)); this.conditionClass.on('conditionResultUpdated', this.handleConditionResult.bind(this));
})); },
destroy() {
this.dragGhost = document.getElementById('js-c-drag-ghost'); this.conditionClass.off('conditionResultUpdated', this.handleConditionResult.bind(this));
}, if (this.conditionClass && typeof this.conditionClass.destroy === 'function') {
updated() { this.conditionClass.destroy();
if (this.isCurrent && this.isCurrent.key === this.condition.key) { delete this.conditionClass;
this.updateCurrentCondition(); }
} },
this.persist(); reset() {
}, this.selectedMetaDataKey = '';
methods: { this.selectedTelemetryKey = '';
dragStart(e) { this.selectOperationName = '';
this.$emit('setMoveIndex', Number(e.target.getAttribute('data-condition-index'))); this.operationValue = '';
},
validate() {
if (this.hasTelemetry() && !this.getTelemetryKey()) {
this.reset();
} else {
if (!this.conditionClass) {
this.initialize();
}
}
}, },
handleConditionResult(args) { handleConditionResult(args) {
this.$emit('conditionResultUpdated', { this.$emit('conditionResultUpdated', {
@ -234,10 +288,11 @@ export default {
}); });
}, },
setOutput() { setOutput() {
if (this.condition.definition.output !== 'false' && this.condition.definition.output !== 'true') { let conditionOutput = this.condition.definition.output;
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
this.selectedOutputKey = this.outputOptions[2].key; this.selectedOutputKey = this.outputOptions[2].key;
} else { } else {
if (this.condition.definition.output === 'true') { if (conditionOutput === 'true') {
this.selectedOutputKey = this.outputOptions[1].key; this.selectedOutputKey = this.outputOptions[1].key;
} else { } else {
this.selectedOutputKey = this.outputOptions[0].key; this.selectedOutputKey = this.outputOptions[0].key;
@ -248,7 +303,11 @@ export default {
if (this.condition.definition.criteria.length && this.condition.definition.criteria[0].operation) { if (this.condition.definition.criteria.length && this.condition.definition.criteria[0].operation) {
for (let i=0, ii=this.operations.length; i < ii; i++) { for (let i=0, ii=this.operations.length; i < ii; i++) {
if (this.condition.definition.criteria[0].operation === this.operations[i].name) { if (this.condition.definition.criteria[0].operation === this.operations[i].name) {
this.selectedOperationKey = this.operations[i].name; this.selectOperationName = this.operations[i].name;
this.comparisonValueField = this.operations[i].inputCount > 0;
if (this.comparisonValueField) {
this.operationValue = this.condition.definition.criteria[0].input[0];
}
} }
} }
} }
@ -258,17 +317,47 @@ export default {
this.openmct.objects.get(this.condition.definition.criteria[0].key).then((obj) => { this.openmct.objects.get(this.condition.definition.criteria[0].key).then((obj) => {
this.telemetryObject = obj; this.telemetryObject = obj;
this.telemetryMetadata = this.openmct.telemetry.getMetadata(this.telemetryObject).values(); this.telemetryMetadata = this.openmct.telemetry.getMetadata(this.telemetryObject).values();
this.selectedMetaDataKey = ''; this.selectedMetaDataKey = this.getTelemetryMetadataKey();
this.selectedTelemetryKey = this.telemetryObject.identifier; this.selectedTelemetryKey = this.getTelemetryKey();
}); });
} else { } else {
this.telemetryObject = null; this.telemetryObject = null;
} }
}, },
getTelemetryMetadataKey() {
let index = -1;
if (this.condition.definition.criteria[0].metaDataKey) {
index = _.findIndex(this.telemetryMetadata, (metadata) => {
return metadata.key === this.condition.definition.criteria[0].metaDataKey;
});
}
return this.telemetryMetadata.length && index > -1 ? this.telemetryMetadata[index].key : '';
},
getTelemetryKey() {
let index = -1;
if (this.condition.definition.criteria[0].key) {
index = _.findIndex(this.telemetry, (obj) => {
let key = this.openmct.objects.makeKeyString(obj.identifier);
let conditionKey = this.openmct.objects.makeKeyString(this.condition.definition.criteria[0].key);
return key === conditionKey;
});
}
return this.telemetry.length && index > -1 ? this.telemetry[index].identifier : '';
},
hasTelemetry() { hasTelemetry() {
return this.condition.definition.criteria.length && this.condition.definition.criteria[0].key; return this.condition.definition.criteria.length && this.condition.definition.criteria[0].key;
}, },
updateConditionCriteria() {
if (this.condition.definition.criteria.length) {
let criterion = this.condition.definition.criteria[0];
criterion.key = this.selectedTelemetryKey;
criterion.metaDataKey = this.selectedMetaDataKey;
criterion.operation = this.selectOperationName;
criterion.input = [this.operationValue];
}
},
persist() { persist() {
this.updateConditionCriteria();
this.openmct.objects.mutate(this.condition, 'definition', this.condition.definition); this.openmct.objects.mutate(this.condition, 'definition', this.condition.definition);
}, },
checkInputValue() { checkInputValue() {
@ -278,20 +367,19 @@ export default {
this.condition.definition.output = this.selectedOutputKey; this.condition.definition.output = this.selectedOutputKey;
} }
}, },
operationKeyChange(ev) { setInputValueVisibility(ev) {
if (ev.target.value !== 'isUndefined' && ev.target.value !== 'isDefined') { for (let i=0, ii=this.operations.length; i < ii; i++) {
this.comparisonValueField = true; if (this.selectOperationName === this.operations[i].name) {
} else { this.comparisonValueField = this.operations[i].inputCount > 0;
this.comparisonValueField = false; break;
}
} }
this.condition.definition.criteria[0].operation = this.selectedOperationKey;
this.persist();
//find the criterion being updated and set the operation property //find the criterion being updated and set the operation property
}, },
getOperationValue(ev) { updateTelemetryMetaData() {
this.condition.definition.criteria[0].input = [ev.target.value]; this.selectedMetaDataKey = '';
this.persist(); this.updateConditionCriteria();
//find the criterion being updated and set the input property this.updateTelemetry();
}, },
updateCurrentCondition() { updateCurrentCondition() {
this.$emit('updateCurrentCondition', this.conditionIdentifier); this.$emit('updateCurrentCondition', this.conditionIdentifier);

View File

@ -1,13 +1,33 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template> <template>
<div class="c-object-view u-contents"> <div class="c-cs-edit w-condition-set">
<div class="c-cs-edit w-condition-set"> <div class="c-sw-edit__ui holder">
<div class="c-sw-edit__ui holder"> <CurrentOutput :condition="currentCondition" />
<CurrentOutput :condition="currentCondition" /> <TestData :is-editing="isEditing" />
<TestData :is-editing="isEditing" /> <ConditionCollection :is-editing="isEditing"
<ConditionCollection :is-editing="isEditing" @currentConditionUpdated="updateCurrentCondition"
@current-condition-updated="updateCurrentCondition" />
/>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template> <template>
<section id="current-output"> <section id="current-output">
<div v-if="condition" <div v-if="condition"

View File

@ -1,83 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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.
*****************************************************************************/
<template>
<div v-show="isValidTarget">
<div
class="c-drop-hint c-drop-hint--always-show"
:class="{'is-mouse-over': isMouseOver}"
@dragover.prevent
@dragenter="dragenter"
@dragleave="dragleave"
@drop="dropHandler"
></div>
</div>
</template>
<script>
export default {
props:{
index: {
type: Number,
required: true
},
allowDrop: {
type: Function,
required: true
}
},
data() {
return {
isMouseOver: false,
isValidTarget: false
}
},
mounted() {
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
document.addEventListener('drop', this.dragend);
},
destroyed() {
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
document.removeEventListener('drop', this.dragend);
},
methods: {
dragenter() {
this.isMouseOver = true;
},
dragleave() {
this.isMouseOver = false;
},
dropHandler(event) {
this.$emit('object-drop-to', this.index, event);
this.isValidTarget = false;
},
dragstart(event) {
this.isValidTarget = this.allowDrop(event, this.index);
},
dragend() {
this.isValidTarget = false;
}
}
}
</script>

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template> <template>
<section v-show="isEditing" <section v-show="isEditing"
id="test-data" id="test-data"

View File

@ -1,6 +1,6 @@
.widget-condition { .widget-condition {
background-color: #eee; background-color: #eee;
margin: 0 0 5px; margin: 0 0 0.33em;
border-radius: 3px; border-radius: 3px;
&--current { &--current {
@ -137,7 +137,14 @@
} }
} }
.c-c__menu-hamburger { .c-c__menu-hamburger {
&:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
&:before { &:before {
content: $glyph-icon-menu-hamburger; content: $glyph-icon-menu-hamburger;
} }
@ -157,9 +164,9 @@
.c-c__drag-ghost { .c-c__drag-ghost {
width: 100%; width: 100%;
min-height: 5px; min-height: 0.33em;
&.dragging { &.dragging {
min-height: 20px; min-height: 2em;
border: solid 1px blue; border: solid 1px blue;
background-color: lightblue; background-color: lightblue;
border-radius: 2px; border-radius: 2px;

View File

@ -21,6 +21,7 @@
*****************************************************************************/ *****************************************************************************/
import * as EventEmitter from 'eventemitter3'; import * as EventEmitter from 'eventemitter3';
import {OPERATIONS} from '../utils/operations';
export default class TelemetryCriterion extends EventEmitter { export default class TelemetryCriterion extends EventEmitter {
@ -38,6 +39,9 @@ export default class TelemetryCriterion extends EventEmitter {
this.objectAPI = this.openmct.objects; this.objectAPI = this.openmct.objects;
this.telemetryAPI = this.openmct.telemetry; this.telemetryAPI = this.openmct.telemetry;
this.id = telemetryDomainObjectDefinition.id; this.id = telemetryDomainObjectDefinition.id;
this.operation = telemetryDomainObjectDefinition.operation;
this.input = telemetryDomainObjectDefinition.input;
this.metaDataKey = telemetryDomainObjectDefinition.metaDataKey;
this.subscription = null; this.subscription = null;
this.telemetryMetadata = null; this.telemetryMetadata = null;
this.telemetryObjectIdAsString = null; this.telemetryObjectIdAsString = null;
@ -51,18 +55,35 @@ export default class TelemetryCriterion extends EventEmitter {
this.emitEvent('criterionUpdated', this); this.emitEvent('criterionUpdated', this);
} }
handleSubscription(datum) { handleSubscription(data) {
let data = this.normalizeData(datum); let result = this.computeResult(data);
this.emitEvent('criterionResultUpdated', { this.emitEvent('criterionResultUpdated', {
result: data, result: result,
error: null error: null
}) })
} }
normalizeData(datum) { findOperation(operation) {
return { for (let i=0, ii=OPERATIONS.length; i < ii; i++) {
[datum.key]: datum[datum.source] if (operation === OPERATIONS[i].name) {
return OPERATIONS[i].operation;
}
} }
return null;
}
computeResult(data) {
let comparator = this.findOperation(this.operation);
let params = [];
let result = false;
params.push(data[this.metaDataKey]);
if (this.input instanceof Array && this.input.length) {
params.push(this.input[0]);
}
if (typeof comparator === 'function') {
result = comparator(params);
}
return result;
} }
emitEvent(eventName, data) { emitEvent(eventName, data) {
@ -76,6 +97,7 @@ export default class TelemetryCriterion extends EventEmitter {
* Subscribes to the telemetry object and returns an unsubscribe function * Subscribes to the telemetry object and returns an unsubscribe function
*/ */
subscribe() { subscribe() {
this.unsubscribe();
this.subscription = this.telemetryAPI.subscribe(this.telemetryObject, (datum) => { this.subscription = this.telemetryAPI.subscribe(this.telemetryObject, (datum) => {
this.handleSubscription(datum); this.handleSubscription(datum);
}); });

View File

@ -94,17 +94,6 @@ describe("The telemetry criterion", function () {
expect(telemetryCriterion.subscription).toBeDefined(); expect(telemetryCriterion.subscription).toBeDefined();
}); });
it("normalizes telemetry data", function () {
let result = telemetryCriterion.normalizeData({
key: 'some-key',
source: 'testSource',
testSource: 'Hello'
});
expect(result).toEqual({
'some-key': 'Hello'
})
});
it("emits update event on new data from telemetry providers", function () { it("emits update event on new data from telemetry providers", function () {
spyOn(telemetryCriterion, 'emitEvent').and.callThrough(); spyOn(telemetryCriterion, 'emitEvent').and.callThrough();
telemetryCriterion.handleSubscription({ telemetryCriterion.handleSubscription({

View File

@ -1,7 +1,16 @@
export const computeConditionForAny = (args) => { export const computeCondition = (resultMap, allMustBeTrue) => {
return false; let result = false;
}; for (let key in resultMap) {
if (resultMap.hasOwnProperty(key)) {
export const computeConditionForAll = (args) => { result = resultMap[key];
return false; if (allMustBeTrue && !result) {
//If we want all conditions to be true, then even one negative result should break.
break;
} else if (!allMustBeTrue && result) {
//If we want at least one condition to be true, then even one positive result should break.
break;
}
}
}
return result;
}; };

View File

@ -1,2 +0,0 @@
import Vue from 'vue';
export const EventBus = new Vue();