Merge remote-tracking branch 'khalidadil/extend-conditional' into combined-rodap-stuff

This commit is contained in:
Scott Bell 2024-09-24 21:40:00 +02:00
commit f78f76818b
17 changed files with 866 additions and 137 deletions

View File

@ -51,7 +51,8 @@ const config = {
compsMathWorker: './src/plugins/comps/CompsMathWorker.js',
espressoTheme: './src/plugins/themes/espresso-theme.scss',
snowTheme: './src/plugins/themes/snow-theme.scss',
darkmatterTheme: './src/plugins/themes/darkmatter-theme.scss'
darkmatterTheme: './src/plugins/themes/darkmatter-theme.scss',
historicalTelemetryWorker: './src/plugins/condition/historicalTelemetryWorker.js',
},
output: {
globalObject: 'this',

View File

@ -66,6 +66,10 @@ module.exports = async (config) => {
{
pattern: 'dist/generatorWorker.js*',
included: false
},
{
pattern: 'dist/historicalTelemetryWorker.js*',
included: false
}
],
port: 9876,

View File

@ -0,0 +1,53 @@
// src/plugins/condition/ConditionInspectorView.js
import mount from 'utils/mount';
import ConditionConfigView from './components/ConditionInspectorConfigView.vue';
export default function ConditionInspectorView(openmct) {
return {
key: 'condition-config',
name: 'Config',
canView: function (selection) {
return selection.length > 0 && selection[0][0].context.item.type === 'conditionSet';
},
view: function (selection) {
let _destroy = null;
const domainObject = selection[0][0].context.item;
return {
show: function (element) {
const { destroy } = mount(
{
el: element,
components: {
ConditionConfigView: ConditionConfigView
},
provide: {
openmct,
domainObject
},
template: '<condition-config-view></condition-config-view>'
},
{
app: openmct.app,
element
}
);
_destroy = destroy;
},
showTab: function (isEditing) {
return isEditing;
},
priority: function () {
return 1;
},
destroy: function () {
if (_destroy) {
_destroy();
}
}
};
}
};
}

View File

@ -24,6 +24,7 @@ import { EventEmitter } from 'eventemitter3';
import { v4 as uuid } from 'uuid';
import Condition from './Condition.js';
import HistoricalTelemetryProvider from './historicalTelemetryProvider.js';
import { getLatestTimestamp } from './utils/time.js';
export default class ConditionManager extends EventEmitter {
@ -39,64 +40,57 @@ export default class ConditionManager extends EventEmitter {
this.shouldEvaluateNewTelemetry = this.shouldEvaluateNewTelemetry.bind(this);
this.compositionLoad = this.composition.load();
this.subscriptions = {};
this.telemetryCollections = {};
this.telemetryObjects = {};
this.testData = {
conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
applied: false
};
this.initialize();
this.telemetryBuffer = [];
this.isProcessing = false;
}
async requestLatestValue(endpoint) {
const options = {
subscribeToTelemetry(telemetryObject) {
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (this.telemetryCollections[keyString]) {
return;
}
const requestOptions = {
size: 1,
strategy: 'latest'
};
const latestData = await this.openmct.telemetry.request(endpoint, options);
if (!latestData) {
throw new Error('Telemetry request failed by returning a falsy response');
}
if (latestData.length === 0) {
return;
}
this.telemetryReceived(endpoint, latestData[0]);
}
subscribeToTelemetry(endpoint) {
const telemetryKeyString = this.openmct.objects.makeKeyString(endpoint.identifier);
if (this.subscriptions[telemetryKeyString]) {
return;
}
const metadata = this.openmct.telemetry.getMetadata(endpoint);
this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
telemetryMetaData: metadata ? metadata.valueMetadatas : []
});
// get latest telemetry value (in case subscription is cached and no new data is coming in)
this.requestLatestValue(endpoint);
this.subscriptions[telemetryKeyString] = this.openmct.telemetry.subscribe(
endpoint,
this.telemetryReceived.bind(this, endpoint)
this.telemetryCollections[keyString] = this.openmct.telemetry.requestCollection(
telemetryObject,
requestOptions
);
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
const telemetryMetaData = metadata ? metadata.valueMetadatas : [];
this.telemetryObjects[keyString] = { ...telemetryObject, telemetryMetaData };
this.telemetryCollections[keyString].on(
'add',
this.telemetryReceived.bind(this, telemetryObject)
);
this.telemetryCollections[keyString].load();
this.updateConditionTelemetryObjects();
}
unsubscribeFromTelemetry(endpointIdentifier) {
const id = this.openmct.objects.makeKeyString(endpointIdentifier);
if (!this.subscriptions[id]) {
console.log('no subscription to remove');
const keyString = this.openmct.objects.makeKeyString(endpointIdentifier);
if (!this.telemetryCollections[keyString]) {
return;
}
this.subscriptions[id]();
delete this.subscriptions[id];
delete this.telemetryObjects[id];
this.telemetryCollections[keyString].destroy();
this.telemetryCollections[keyString] = null;
this.telemetryObjects[keyString] = null;
this.removeConditionTelemetryObjects();
//force re-computation of condition set result as we might be in a state where
@ -107,7 +101,7 @@ export default class ConditionManager extends EventEmitter {
this.timeSystems,
this.openmct.time.getTimeSystem()
);
this.updateConditionResults({ id: id });
this.updateConditionResults({ id: keyString });
this.updateCurrentCondition(latestTimestamp);
if (Object.keys(this.telemetryObjects).length === 0) {
@ -329,6 +323,19 @@ export default class ConditionManager extends EventEmitter {
return currentCondition;
}
getHistoricalData() {
if (!this.conditionSetDomainObject.configuration.shouldFetchHistorical) {
return [];
}
const historicalTelemetry = new HistoricalTelemetryProvider(
this.openmct,
this.telemetryObjects,
this.conditions,
this.conditionSetDomainObject
);
return historicalTelemetry.getHistoricalData();
}
getCurrentConditionLAD(conditionResults) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length - 1];
@ -384,8 +391,26 @@ export default class ConditionManager extends EventEmitter {
}
const currentCondition = this.getCurrentConditionLAD(conditionResults);
let output = currentCondition?.configuration?.output;
if (output === 'telemetry value') {
const { outputTelemetry, outputMetadata } = currentCondition.configuration;
const outputTelemetryObject = await this.openmct.objects.get(outputTelemetry);
const telemetryOptions = {
size: 1,
strategy: 'latest',
timeContext: this.openmct.time.getContextForView([])
};
const latestData = await this.openmct.telemetry.request(
outputTelemetryObject,
telemetryOptions
);
if (latestData?.[0]?.[outputMetadata]) {
output = latestData?.[0]?.[outputMetadata];
}
}
const currentOutput = {
output: currentCondition.configuration.output,
output: output,
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id,
...latestTimestamp
@ -403,6 +428,18 @@ export default class ConditionManager extends EventEmitter {
}
}
const conditionTelemetries = [];
const conditions = this.conditionSetDomainObject.configuration.conditionCollection;
conditions.forEach((condition) => {
if (condition?.configuration?.outputTelemetry) {
conditionTelemetries.push(condition?.configuration?.outputTelemetry);
}
});
if (conditionTelemetries.includes(id)) {
return true;
}
return false;
}
@ -410,11 +447,13 @@ export default class ConditionManager extends EventEmitter {
return this.openmct.time.getBounds().end >= currentTimestamp;
}
telemetryReceived(endpoint, datum) {
telemetryReceived(endpoint, data) {
if (!this.isTelemetryUsed(endpoint)) {
return;
}
const datum = data[0];
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
const timeSystemKey = this.openmct.time.getTimeSystem().key;
let timestamp = {};
@ -422,7 +461,7 @@ export default class ConditionManager extends EventEmitter {
timestamp[timeSystemKey] = currentTimestamp;
if (this.shouldEvaluateNewTelemetry(currentTimestamp)) {
this.updateConditionResults(normalizedDatum);
this.updateCurrentCondition(timestamp);
this.updateCurrentCondition(timestamp, endpoint, datum);
}
}
@ -435,14 +474,12 @@ export default class ConditionManager extends EventEmitter {
});
}
updateCurrentCondition(timestamp) {
const currentCondition = this.getCurrentCondition();
emitConditionSetResult(currentCondition, timestamp, outputValue) {
this.emit(
'conditionSetResultUpdated',
Object.assign(
{
output: currentCondition.configuration.output,
output: outputValue,
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
@ -451,6 +488,60 @@ export default class ConditionManager extends EventEmitter {
);
}
updateCurrentCondition(timestamp, telemetryObject, telemetryData) {
this.telemetryBuffer.push({ timestamp, telemetryObject, telemetryData });
if (!this.isProcessing) {
this.processBuffer();
}
}
async processBuffer() {
this.isProcessing = true;
while (this.telemetryBuffer.length > 0) {
const { timestamp, telemetryObject, telemetryData } = this.telemetryBuffer.shift();
await this.processCondition(timestamp, telemetryObject, telemetryData);
}
this.isProcessing = false;
}
async processCondition(timestamp, telemetryObject, telemetryData) {
const currentCondition = this.getCurrentCondition();
let telemetryValue = currentCondition.configuration.output;
if (currentCondition?.configuration?.outputTelemetry) {
const selectedOutputIdentifier = currentCondition?.configuration?.outputTelemetry;
const outputMetadata = currentCondition?.configuration?.outputMetadata;
const telemetryKeystring = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (selectedOutputIdentifier === telemetryKeystring) {
telemetryValue = telemetryData[outputMetadata];
} else {
const outputTelemetryObject = await this.openmct.objects.get(selectedOutputIdentifier);
const telemetryOptions = {
size: 1,
strategy: 'latest',
start: timestamp?.utc - 1000,
end: timestamp?.utc + 1000
};
const outputTelemetryData = await this.openmct.telemetry.request(
outputTelemetryObject,
telemetryOptions
);
const outputTelemetryValue =
outputTelemetryData?.length > 0 ? outputTelemetryData.slice(-1)[0] : null;
if (outputTelemetryData.length && outputTelemetryValue?.[outputMetadata]) {
telemetryValue = outputTelemetryValue?.[outputMetadata];
} else {
telemetryValue = undefined;
}
}
}
this.emitConditionSetResult(currentCondition, timestamp, telemetryValue);
}
getTestData(metadatum) {
let data = undefined;
if (this.testData.applied) {
@ -507,8 +598,9 @@ export default class ConditionManager extends EventEmitter {
destroy() {
this.composition.off('add', this.subscribeToTelemetry, this);
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
Object.values(this.subscriptions).forEach((unsubscribe) => unsubscribe());
delete this.subscriptions;
Object.values(this.telemetryCollections).forEach((telemetryCollection) =>
telemetryCollection.destroy()
);
this.conditions.forEach((condition) => {
condition.destroy();

View File

@ -42,8 +42,9 @@ export default class ConditionSetTelemetryProvider {
async request(domainObject, options) {
let conditionManager = this.getConditionManager(domainObject);
const formattedHistoricalData = await conditionManager.getHistoricalData();
let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
return latestOutput;
return [...formattedHistoricalData, ...latestOutput];
}
subscribe(domainObject, callback) {

View File

@ -235,10 +235,11 @@ export default {
return arr;
},
addTelemetryObject(domainObject) {
async addTelemetryObject(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
const telemetryPath = await this.getFullTelemetryPath(domainObject);
this.telemetryObjs.push(domainObject);
this.telemetryObjs.push({ ...domainObject, path: telemetryPath });
this.$emit('telemetry-updated', this.telemetryObjs);
this.subscribeToStaleness(domainObject, (stalenessResponse) => {
@ -248,6 +249,19 @@ export default {
});
});
},
async getFullTelemetryPath(telemetry) {
const keyString = this.openmct.objects.makeKeyString(telemetry.identifier);
const originalPathObjects = await this.openmct.objects.getOriginalPath(keyString, []);
const telemetryPath = originalPathObjects.reverse().map((pathObject) => {
if (pathObject.type !== 'root') {
return pathObject.name;
}
return undefined;
});
return telemetryPath.join('/');
},
removeTelemetryObject(identifier) {
const keyString = this.openmct.objects.makeKeyString(identifier);
const index = this.telemetryObjs.findIndex((obj) => {

View File

@ -0,0 +1,62 @@
<template>
<div class="c-inspect-properties">
<h2>Configuration</h2>
<section>
<div class="c-form-row">
<label for="historical-toggle">Enable Historical: </label>
<ToggleSwitch
id="historical-toggle"
class="c-toggle-switch"
:checked="historicalEnabled"
name="condition-historical-toggle"
@change="onToggleChange"
/>
</div>
</section>
</div>
</template>
<script>
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
export default {
components: {
ToggleSwitch
},
inject: ['openmct', 'domainObject'],
data() {
return {
historicalEnabled: false
};
},
mounted() {
this.historicalEnabled = this.domainObject.configuration.shouldFetchHistorical;
},
methods: {
onToggleChange() {
this.historicalEnabled = !this.historicalEnabled;
this.openmct.objects.mutate(
this.domainObject,
'configuration.shouldFetchHistorical',
this.historicalEnabled
);
}
}
};
</script>
<style scoped>
.c-inspect-properties {
padding: 10px;
}
.c-form-row {
display: flex;
align-items: center;
margin-bottom: 10px;
}
label {
margin-right: 10px;
}
</style>

View File

@ -99,13 +99,13 @@
@change="setOutputValue"
>
<option v-for="option in outputOptions" :key="option" :value="option">
{{ initCap(option) }}
{{ option }}
</option>
</select>
</span>
<span class="c-cdef__control">
<input
v-if="selectedOutputSelection === outputOptions[2]"
v-if="selectedOutputSelection === outputOptions[3]"
v-model="condition.configuration.output"
aria-label="Condition Output String"
class="t-condition-name-input"
@ -113,8 +113,41 @@
@change="persist"
/>
</span>
<span v-if="selectedOutputSelection === 'telemetry value'" class="c-cdef__control">
<select
v-model="condition.configuration.outputTelemetry"
aria-label="Output Telemetry Selection"
@change="persist"
>
<option value="">- Select Telemetry -</option>
<option
v-for="telemetryOption in telemetry"
:key="openmct.objects.makeKeyString(telemetryOption.identifier)"
:value="openmct.objects.makeKeyString(telemetryOption.identifier)"
>
{{ telemetryOption.path }}
</option>
</select>
</span>
<span v-if="condition.configuration.outputTelemetry" class="c-cdef__control">
<select
v-model="condition.configuration.outputMetadata"
aria-label="Output Telemetry Metadata Selection"
@change="persist"
>
<option value="">- Select Field -</option>
<option
v-for="(option, index) in telemetryMetadataOptions[
condition.configuration.outputTelemetry
]"
:key="index"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
</span>
<div v-if="!condition.isDefault" class="c-cdef__match-and-criteria">
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Match</span>
@ -181,7 +214,12 @@
<span class="c-condition__name">
{{ condition.configuration.name }}
</span>
<span class="c-condition__output"> Output: {{ condition.configuration.output }} </span>
<span class="c-condition__output">
Output:
{{
condition.configuration.output === undefined ? 'none' : condition.configuration.output
}}
</span>
</div>
<div class="c-condition__summary">
<ConditionDescription :show-label="false" :condition="condition" />
@ -250,10 +288,11 @@ export default {
expanded: true,
trigger: 'all',
selectedOutputSelection: '',
outputOptions: ['false', 'true', 'string'],
outputOptions: ['none', 'false', 'true', 'string', 'telemetry value'],
criterionIndex: 0,
draggingOver: false,
isDefault: this.condition.isDefault
isDefault: this.condition.isDefault,
telemetryMetadataOptions: {}
};
},
computed: {
@ -287,26 +326,51 @@ export default {
return false;
}
},
watch: {
condition: {
handler() {
if (this.condition.configuration.output !== 'telemetry value') {
this.condition.configuration.outputTelemetry = null;
this.condition.configuration.outputMetadata = null;
}
},
deep: true
},
isEditing(newValue, oldValue) {
if (newValue === true) {
this.initializeMetadata();
}
}
},
unmounted() {
this.destroy();
},
mounted() {
this.setOutputSelection();
this.initializeMetadata();
},
methods: {
setOutputSelection() {
let conditionOutput = this.condition.configuration.output;
if (conditionOutput) {
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
if (
conditionOutput !== 'false' &&
conditionOutput !== 'true' &&
conditionOutput !== 'telemetry value'
) {
this.selectedOutputSelection = 'string';
} else {
this.selectedOutputSelection = conditionOutput;
}
} else if (conditionOutput === undefined) {
this.selectedOutputSelection = 'none';
}
},
setOutputValue() {
if (this.selectedOutputSelection === 'string') {
this.condition.configuration.output = '';
} else if (this.selectedOutputSelection === 'none') {
this.condition.configuration.output = undefined;
} else {
this.condition.configuration.output = this.selectedOutputSelection;
}
@ -401,6 +465,24 @@ export default {
},
initCap(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
initializeMetadata() {
this.telemetry.forEach((telemetryObject) => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
if (telemetryMetadata) {
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
} else {
this.telemetryMetadataOptions[id] = [];
}
});
},
getId(identifier) {
if (identifier) {
return this.openmct.objects.makeKeyString(identifier);
}
return [];
}
}
};

View File

@ -40,7 +40,7 @@
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
{{ telemetryOption.path }}
</option>
</select>
</span>

View File

@ -63,7 +63,7 @@
:key="index"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
{{ telemetryPaths[index] || telemetryOption.name }}
</option>
</select>
</span>
@ -147,7 +147,8 @@ export default {
expanded: true,
isApplied: false,
testInputs: [],
telemetryMetadataOptions: {}
telemetryMetadataOptions: {},
telemetryPaths: []
};
},
watch: {
@ -200,6 +201,10 @@ export default {
this.telemetryMetadataOptions[id] = [];
}
});
this.telemetry.forEach(async (telemetryOption, index) => {
const telemetryPath = await this.getFullTelemetryPath(telemetryOption);
this.telemetryPaths[index] = telemetryPath;
});
},
addTestInput(testInput) {
this.testInputs.push(
@ -244,6 +249,22 @@ export default {
applied: this.isApplied,
conditionTestInputs: this.testInputs
});
},
async getFullTelemetryPath(telemetry) {
const keyStringForObject = this.openmct.objects.makeKeyString(telemetry.identifier);
const originalPathObjects = await this.openmct.objects.getOriginalPath(
keyStringForObject,
[]
);
const telemetryPath = originalPathObjects.reverse().map((pathObject) => {
if (pathObject.type !== 'root') {
return pathObject.name;
}
return undefined;
});
return telemetryPath.join('/');
}
}
};

View File

@ -0,0 +1,287 @@
export default class HistoricalTelemetryProvider {
constructor(openmct, telemetryObjects, conditions, conditionSetDomainObject) {
this.openmct = openmct;
this.telemetryObjects = telemetryObjects;
this.bounds = { start: null, end: null };
this.telemetryList = [];
this.conditions = conditions;
this.conditionSetDomainObject = conditionSetDomainObject;
this.historicalTelemetryPoolMap = new Map();
this.historicalTelemetryDateMap = new Map();
this.index = 0;
}
setTimeBounds(bounds) {
this.bounds = bounds;
}
refreshAllHistoricalTelemetries() {
const refreshPromises = [];
for (const [, value] of Object.entries(this.telemetryObjects)) {
refreshPromises.push(this.refreshHistoricalTelemetry(value));
}
return Promise.all(refreshPromises);
}
async refreshHistoricalTelemetry(domainObject, identifier) {
console.log('refreshHistoricalTelemetry');
if (!domainObject && identifier) {
domainObject = await this.openmct.objects.get(identifier);
}
const id = this.openmct.objects.makeKeyString(domainObject.identifier);
const telemetryOptions = { ...this.bounds };
const historicalTelemetry = await this.openmct.telemetry.request(
domainObject,
telemetryOptions
);
this.historicalTelemetryPoolMap.set(id, { domainObject, historicalTelemetry });
return { domainObject, historicalTelemetry };
}
evaluateTrueCondition(historicalDateMap, timestamp, condition, conditionCollectionMap) {
const telemetryData = historicalDateMap.get(timestamp);
const conditionConfiguration = conditionCollectionMap.get(condition.id)?.configuration;
const { outputTelemetry, outputMetadata } = conditionConfiguration;
let output = {};
if (outputTelemetry) {
const outputTelemetryID = this.openmct.objects.makeKeyString(outputTelemetry);
const outputTelemetryData = telemetryData.get(outputTelemetryID);
output.telemetry = outputTelemetryData;
output.value = outputTelemetryData[outputMetadata];
output.condition = condition;
} else if (conditionConfiguration?.output) {
output.telemetry = null;
output.value = conditionConfiguration?.output;
output.condition = condition;
}
return output;
}
async getAllTelemetries(conditionCollection) {
const conditionCollectionMap = new Map();
const inputTelemetries = [];
const outputTelemetries = [];
const historicalTelemetryPoolPromises = [];
conditionCollection.forEach((condition, index) => {
console.log('-------------------------');
console.log(condition);
const { id } = condition;
const { criteria, output, outputTelemetry, outputMetadata } = condition.configuration;
const inputTelemetry = criteria?.[0]?.telemetry;
console.log(id);
console.log(criteria);
console.log(output);
console.log(outputMetadata);
console.log('inputTelemetry', inputTelemetry);
console.log('outputTelemetry', outputTelemetry);
conditionCollectionMap.set(condition?.id, condition);
if (inputTelemetry) {
const inputTelemetryId = this.openmct.objects.makeKeyString(inputTelemetry);
if (![...inputTelemetries, ...outputTelemetries].includes(inputTelemetryId)) {
historicalTelemetryPoolPromises.push(
this.refreshHistoricalTelemetry(null, inputTelemetry)
);
}
inputTelemetries.push(inputTelemetryId);
} else {
inputTelemetries.push(null);
}
if (outputTelemetry) {
if (![...inputTelemetries, ...outputTelemetries].includes(outputTelemetry)) {
historicalTelemetryPoolPromises.push(
this.refreshHistoricalTelemetry(null, outputTelemetry)
);
}
outputTelemetries.push(outputTelemetry);
} else {
outputTelemetries.push(null);
}
});
const historicalTelemetriesPool = await Promise.all(historicalTelemetryPoolPromises);
return {
historicalTelemetriesPool,
inputTelemetries,
outputTelemetries,
conditionCollectionMap
};
}
sortTelemetriesByDate(historicalTelemetriesPool) {
const historicalTelemetryDateMap = new Map();
historicalTelemetriesPool.forEach((historicalTelemetryList) => {
const { historicalTelemetry, domainObject } = historicalTelemetryList;
const { identifier } = domainObject;
const telemetryIdentifier = this.openmct.objects.makeKeyString(identifier);
historicalTelemetry.forEach((historicalTelemetryItem) => {
if (!historicalTelemetryDateMap.get(historicalTelemetryItem.utc)) {
const telemetryMap = new Map();
telemetryMap.set(telemetryIdentifier, historicalTelemetryItem);
historicalTelemetryDateMap.set(historicalTelemetryItem.utc, telemetryMap);
} else {
const telemetryMap = historicalTelemetryDateMap.get(historicalTelemetryItem.utc);
telemetryMap.set(telemetryIdentifier, historicalTelemetryItem);
historicalTelemetryDateMap.set(historicalTelemetryItem.utc, telemetryMap);
}
});
});
return historicalTelemetryDateMap;
}
async sortTelemetriesInWorker(historicalTelemetriesPool) {
const sortedTelemetries = await this.startWorker('sortTelemetries', {
historicalTelemetriesPool
});
return sortedTelemetries;
}
async startWorker(type, data) {
// eslint-disable-next-line no-undef
const workerUrl = `${this.openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}historicalTelemetryWorker.js`;
const worker = new Worker(workerUrl);
try {
const result = await this.getDataFromWorker(worker, type, data);
return result;
} catch (error) {
console.error('Error in condition manager getHistoricalData:', error);
throw error;
} finally {
worker.terminate();
}
}
getDataFromWorker(worker, type, data) {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
if (e.data.type === 'result') {
resolve(e.data.data);
} else if (e.data.type === 'error') {
reject(new Error(e.data.error));
}
};
worker.onerror = (error) => {
reject(error);
};
worker.postMessage({
type,
data
});
});
}
evaluateConditionsByDate(historicalTelemetryDateMap, conditionCollectionMap) {
const outputTelemetryDateMap = new Map();
historicalTelemetryDateMap.forEach((historicalTelemetryMap, timestamp) => {
let isConditionValid = false;
const evaluatedConditions = [];
this.conditions.forEach((condition) => {
if (isConditionValid) {
return;
}
const { id } = condition;
const conditionMetadata = { condition };
const conditionCriteria = condition.criteria[0];
let result;
if (conditionCriteria?.telemetry) {
const conditionInputTelemetryId = this.openmct.objects.makeKeyString(
conditionCriteria.telemetry
);
const inputTelemetry = historicalTelemetryMap.get(conditionInputTelemetryId);
conditionMetadata.inputTelemetry = inputTelemetry;
result = conditionCriteria.computeResult({
id,
...inputTelemetry
});
} else if (!conditionCriteria) {
const conditionDetails = conditionCollectionMap.get(id);
const { isDefault } = conditionDetails;
const conditionConfiguration = conditionDetails?.configuration;
const { outputTelemetry, outputMetadata, output } = conditionConfiguration;
if (isDefault) {
const conditionOutput = {
telemetry: null,
value: output,
condition
};
outputTelemetryDateMap.set(timestamp, conditionOutput);
}
}
conditionMetadata.result = result;
evaluatedConditions.push(conditionMetadata);
if (result === true) {
isConditionValid = true;
const conditionOutput = this.evaluateTrueCondition(
historicalTelemetryDateMap,
timestamp,
condition,
conditionCollectionMap
);
outputTelemetryDateMap.set(timestamp, conditionOutput);
}
});
});
return outputTelemetryDateMap;
}
async getHistoricalInputsByDate() {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
const {
historicalTelemetriesPool,
inputTelemetries,
outputTelemetries,
conditionCollectionMap
} = await this.getAllTelemetries(conditionCollection);
const historicalTelemetryDateMap =
await this.sortTelemetriesInWorker(historicalTelemetriesPool);
const outputTelemetryDateMap = this.evaluateConditionsByDate(
historicalTelemetryDateMap,
conditionCollectionMap
);
console.log('*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣*️⃣');
console.log(historicalTelemetriesPool);
console.log(this.historicalTelemetryPoolMap);
console.log(inputTelemetries);
console.log(outputTelemetries);
console.log(historicalTelemetryDateMap);
console.log(outputTelemetryDateMap);
return outputTelemetryDateMap;
}
addItemToHistoricalTelemetryMap(telemetryMap, item, type, index) {
if (type === 'input') {
telemetryMap.set();
}
}
async getHistoricalData() {
console.log('getHistoricalData');
this.setTimeBounds(this.openmct.time.getBounds());
const outputTelemetryMap = await this.getHistoricalInputsByDate();
const formattedOutputTelemetry = this.formatOutputData(outputTelemetryMap);
console.log(formattedOutputTelemetry);
return formattedOutputTelemetry;
}
formatOutputData(outputTelemetryMap) {
const outputTelemetryList = [];
const domainObject = this.conditionSetDomainObject;
outputTelemetryMap.forEach((outputMetadata, timestamp) => {
const { condition, telemetry, value } = outputMetadata;
outputTelemetryList.push({
conditionId: condition.id,
id: domainObject.identifier,
output: value,
utc: timestamp
});
});
return outputTelemetryList;
}
}

View File

@ -0,0 +1,33 @@
import { makeKeyString } from '../../api/objects/object-utils.js';
function sortTelemetriesByDate(historicalTelemetriesPool) {
const historicalTelemetryDateMap = new Map();
historicalTelemetriesPool.forEach((historicalTelemetryList) => {
const { historicalTelemetry, domainObject } = historicalTelemetryList;
const { identifier } = domainObject;
const telemetryIdentifier = makeKeyString(identifier);
historicalTelemetry.forEach((historicalTelemetryItem) => {
if (!historicalTelemetryDateMap.get(historicalTelemetryItem.utc)) {
const telemetryMap = new Map();
telemetryMap.set(telemetryIdentifier, historicalTelemetryItem);
historicalTelemetryDateMap.set(historicalTelemetryItem.utc, telemetryMap);
} else {
const telemetryMap = historicalTelemetryDateMap.get(historicalTelemetryItem.utc);
telemetryMap.set(telemetryIdentifier, historicalTelemetryItem);
historicalTelemetryDateMap.set(historicalTelemetryItem.utc, telemetryMap);
}
});
});
return historicalTelemetryDateMap;
}
self.onmessage = function (e) {
const { type, data } = e.data;
if (type === 'sortTelemetries') {
const sortedTelemetries = sortTelemetriesByDate(data.historicalTelemetriesPool);
self.postMessage({ type: 'result', data: sortedTelemetries });
} else {
self.postMessage({ type: 'error', error: 'Unknown message type' });
}
};

View File

@ -21,6 +21,7 @@
*****************************************************************************/
import { v4 as uuid } from 'uuid';
import ConditionInspectorViewProvider from './ConditionInspectorViewProvider.js';
import ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy.js';
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider.js';
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider.js';
@ -37,6 +38,7 @@ export default function ConditionPlugin() {
cssClass: 'icon-conditional',
initialize: function (domainObject) {
domainObject.configuration = {
shouldFetchHistorical: false,
conditionTestData: [],
conditionCollection: [
{
@ -61,5 +63,6 @@ export default function ConditionPlugin() {
openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct));
openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct));
openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct));
openmct.inspectorViews.addProvider(new ConditionInspectorViewProvider(openmct));
};
}

View File

@ -720,50 +720,69 @@ describe('the plugin', function () {
};
});
it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
it('should evaluate as old when telemetry is not received in the allotted time', async () => {
let onAddResolve;
const onAddCalledPromise = new Promise((resolve) => {
onAddResolve = resolve;
});
const mockTelemetryCollection = {
load: jasmine.createSpy('load'),
on: jasmine.createSpy('on').and.callFake((event, callback) => {
if (event === 'add') {
onAddResolve();
}
})
};
openmct.telemetry = jasmine.createSpyObj('telemetry', [
'subscribe',
'getMetadata',
'request',
'getValueFormatter',
'abortAllRequests'
'abortAllRequests',
'requestCollection'
]);
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
openmct.telemetry.getMetadata.and.returnValue({
...testTelemetryObject.telemetry,
valueMetadatas: []
valueMetadatas: testTelemetryObject.telemetry.values,
valuesForHints: jasmine
.createSpy('valuesForHints')
.and.returnValue(testTelemetryObject.telemetry.values),
value: jasmine.createSpy('value').and.callFake((key) => {
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
})
});
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
openmct.telemetry.requestCollection.and.returnValue(mockTelemetryCollection);
openmct.telemetry.getValueFormatter.and.returnValue({
parse: function (value) {
return value;
}
});
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
'test-object': testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Any old telemetry',
id: {
namespace: '',
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
},
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
utc: undefined
});
done();
}, 400);
// Wait for the 'on' callback to be called
await onAddCalledPromise;
// Simulate the passage of time and no data received
await new Promise((resolve) => setTimeout(resolve, 400));
expect(mockListener).toHaveBeenCalledWith({
output: 'Any old telemetry',
id: {
namespace: '',
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
},
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
utc: undefined
});
});
it('should not evaluate as old when telemetry is received in the allotted time', (done) => {
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
openmct.telemetry.getMetadata.and.returnValue({
...testTelemetryObject.telemetry,
valueMetadatas: testTelemetryObject.telemetry.values
});
it('should not evaluate as old when telemetry is received in the allotted time', async () => {
const testDatum = {
'some-key2': '',
utc: 1,
@ -771,8 +790,49 @@ describe('the plugin', function () {
'some-key': null,
id: 'test-object'
};
openmct.telemetry.request = jasmine.createSpy('request');
let onAddResolve;
let onAddCallback;
const onAddCalledPromise = new Promise((resolve) => {
onAddResolve = resolve;
});
const mockTelemetryCollection = {
load: jasmine.createSpy('load'),
on: jasmine.createSpy('on').and.callFake((event, callback) => {
if (event === 'add') {
onAddCallback = callback;
onAddResolve();
}
})
};
openmct.telemetry = jasmine.createSpyObj('telemetry', [
'getMetadata',
'getValueFormatter',
'request',
'subscribe',
'requestCollection'
]);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.request.and.returnValue(Promise.resolve([testDatum]));
openmct.telemetry.getMetadata.and.returnValue({
...testTelemetryObject.telemetry,
valueMetadatas: testTelemetryObject.telemetry.values,
valuesForHints: jasmine
.createSpy('valuesForHints')
.and.returnValue(testTelemetryObject.telemetry.values),
value: jasmine.createSpy('value').and.callFake((key) => {
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
})
});
openmct.telemetry.requestCollection.and.returnValue(mockTelemetryCollection);
openmct.telemetry.getValueFormatter.and.returnValue({
parse: function (value) {
return value;
}
});
const date = 1;
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
['0.4'];
@ -782,19 +842,25 @@ describe('the plugin', function () {
'test-object': testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Default',
id: {
namespace: '',
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
},
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
utc: date
});
done();
}, 300);
// Wait for the 'on' callback to be called
await onAddCalledPromise;
// Simulate receiving telemetry data
onAddCallback([testDatum]);
// Wait a bit for the condition manager to process the data
await new Promise((resolve) => setTimeout(resolve, 100));
expect(mockListener).toHaveBeenCalledWith({
output: 'Default',
id: {
namespace: '',
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
},
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
utc: date
});
});
});
@ -902,17 +968,25 @@ describe('the plugin', function () {
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
openmct.telemetry.getMetadata.and.returnValue({
...testTelemetryObject.telemetry,
valueMetadatas: []
valueMetadatas: testTelemetryObject.telemetry.values,
valuesForHints: jasmine
.createSpy('valuesForHints')
.and.returnValue(testTelemetryObject.telemetry.values),
value: jasmine.createSpy('value').and.callFake((key) => {
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
})
});
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
'test-object': testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
conditionMgr.telemetryReceived(testTelemetryObject, {
'some-key': 2,
utc: date
});
conditionMgr.telemetryReceived(testTelemetryObject, [
{
'some-key': 2,
utc: date
}
]);
let result = conditionMgr.conditions.map((condition) => condition.result);
expect(result[2]).toBeUndefined();
});
@ -1002,26 +1076,37 @@ describe('the plugin', function () {
}
};
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
// const mockTransactionService = jasmine.createSpyObj(
// 'transactionService',
// ['commit']
// );
openmct.telemetry = jasmine.createSpyObj('telemetry', [
'isTelemetryObject',
'request',
'subscribe',
'getMetadata',
'getValueFormatter',
'request'
'requestCollection'
]);
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.telemetry.getMetadata.and.returnValue({
...testTelemetryObject.telemetry,
valueMetadatas: testTelemetryObject.telemetry.values,
valuesForHints: jasmine
.createSpy('valuesForHints')
.and.returnValue(testTelemetryObject.telemetry.values),
value: jasmine.createSpy('value').and.callFake((key) => {
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
})
});
openmct.telemetry.getValueFormatter.and.returnValue({
parse: function (value) {
return value;
}
});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
openmct.telemetry.requestCollection.and.returnValue({
load: jasmine.createSpy('load'),
on: jasmine.createSpy('on')
});
const styleRuleManger = new StyleRuleManager(stylesObject, openmct, null, true);
spyOn(styleRuleManger, 'subscribeToConditionSet');

View File

@ -243,6 +243,7 @@ export default {
domainObject: {
...this.childObject,
configuration: {
...this.childObject.configuration,
series: [
{
identifier: this.childObject.identifier,

View File

@ -128,36 +128,26 @@ export default {
}
},
updateStyle(styleObj) {
let elemToStyle = this.getStyleReceiver();
const elemToStyle = this.getStyleReceiver();
if (!styleObj || elemToStyle === undefined) {
if (!styleObj || !elemToStyle) {
return;
}
let keys = Object.keys(styleObj);
// handle visibility separately
if (styleObj.isStyleInvisible !== undefined) {
elemToStyle.classList.toggle(STYLE_CONSTANTS.isStyleInvisible, styleObj.isStyleInvisible);
styleObj.isStyleInvisible = null;
}
keys.forEach((key) => {
if (elemToStyle) {
if (typeof styleObj[key] === 'string' && styleObj[key].indexOf('__no_value') > -1) {
if (elemToStyle.style[key]) {
elemToStyle.style[key] = '';
}
requestAnimationFrame(() => {
Object.entries(styleObj).forEach(([key, value]) => {
if (typeof value !== 'string' || !value.includes('__no_value')) {
elemToStyle.style[key] = value;
} else {
if (
!styleObj.isStyleInvisible &&
elemToStyle.classList.contains(STYLE_CONSTANTS.isStyleInvisible)
) {
elemToStyle.classList.remove(STYLE_CONSTANTS.isStyleInvisible);
} else if (
styleObj.isStyleInvisible &&
!elemToStyle.classList.contains(styleObj.isStyleInvisible)
) {
elemToStyle.classList.add(styleObj.isStyleInvisible);
}
elemToStyle.style[key] = styleObj[key];
elemToStyle.style[key] = ''; // remove the property
}
}
});
});
}
}

View File

@ -94,7 +94,7 @@ export function ticks(start, stop, count) {
}
export function commonPrefix(a, b) {
const maxLen = Math.min(a.length, b.length);
const maxLen = Math.min(a.length, b?.length);
let breakpoint = 0;
for (let i = 0; i < maxLen; i++) {
if (a[i] !== b[i]) {
@ -110,7 +110,7 @@ export function commonPrefix(a, b) {
}
export function commonSuffix(a, b) {
const maxLen = Math.min(a.length, b.length);
const maxLen = Math.min(a.length, b?.length);
let breakpoint = 0;
for (let i = 0; i <= maxLen; i++) {
if (a[a.length - i] !== b[b.length - i]) {