Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b4fdabf
adding telemetry collections to condition manager
jvigliotta Sep 12, 2024
5bd5ca9
handling telemetry collection data not datum
jvigliotta Sep 12, 2024
4ab19e3
adding from maaster
jvigliotta Sep 12, 2024
b467f9c
addressing PR comments
jvigliotta Sep 16, 2024
3f66140
Merge branch 'master' into conditional-style-performance
jvigliotta Sep 16, 2024
6482135
update unit test to work with telemetry collections
jvigliotta Sep 17, 2024
54c90e0
fixing tests
jvigliotta Sep 17, 2024
94a4ff3
removing unnecessary addition
jvigliotta Sep 17, 2024
7cba09b
removing focused describe
jvigliotta Sep 17, 2024
405b497
removing focused it
jvigliotta Sep 17, 2024
1581216
Merge branch 'master' into conditional-style-performance
jvigliotta Sep 17, 2024
5b385ea
fix weird test bleed
jvigliotta Sep 17, 2024
73489cd
Merge branch 'conditional-style-performance' of https://github.com/na…
jvigliotta Sep 17, 2024
23cf829
Add realtime output of telemetry data in conditionals and add support…
khalidadil Sep 19, 2024
21e94fd
Cleanup and add missing files
khalidadil Sep 21, 2024
101baa5
Fix issue with missing data
khalidadil Sep 30, 2024
7896f36
Update emitted values
khalidadil Sep 30, 2024
4b39ea2
Addressing feedback
khalidadil Sep 30, 2024
b975554
Creating a const for telemetry value
khalidadil Oct 1, 2024
7727a90
Cleanup
khalidadil Oct 1, 2024
f544a1d
Pass through plot options
khalidadil Oct 1, 2024
1180597
Cleanup
khalidadil Oct 2, 2024
39a73cf
Fix problem introduced with const
khalidadil Oct 3, 2024
0f2f71f
Add back initialize on mount
khalidadil Oct 3, 2024
4a3b0b9
Compensate for missing data at certain timestamps
khalidadil Oct 3, 2024
a739d61
Rename file
khalidadil Oct 3, 2024
488178a
Rename file
khalidadil Oct 3, 2024
f239d4b
Update metadata provider
khalidadil Oct 4, 2024
f4c2b79
Update condition set metadata
khalidadil Oct 11, 2024
bd3f00c
Fix zero issue
khalidadil Oct 11, 2024
cba2056
Try removing the default format for better data inspection
khalidadil Oct 11, 2024
848fbb9
merging fork
jvigliotta Aug 26, 2025
7702e8f
pulled over changes that were made after copying whole repo in a dump
jvigliotta Aug 26, 2025
a88fa39
pulling over from file dump
jvigliotta Aug 26, 2025
4611faa
remove debug
jvigliotta Aug 26, 2025
f057602
handle "none" output situations
jvigliotta Aug 27, 2025
a992d32
removing telemetry when output condition is "none"
jvigliotta Aug 27, 2025
3d4eb3f
adding license info
jvigliotta Aug 27, 2025
2b4d785
one last tweak to get none to work correctly without messin up other …
jvigliotta Aug 27, 2025
559eb30
Merge branch 'master' into feature/historical-conditions
jvigliotta Aug 27, 2025
4cba005
Merge branch 'master' into feature/historical-conditions
jvigliotta Oct 6, 2025
d9cc238
Merge branch 'master' into feature/historical-conditions
jvigliotta Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .webpack/webpack.common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ const config = {
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.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',
Expand Down
17 changes: 17 additions & 0 deletions e2e/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@ async function createDomainObjectWithDefaults(
};
}

/**
* Retrieves the properties of an OpenMCT domain object by its identifier.
*
* @param {import('@playwright/test').Page} page - The Playwright page object.
* @param {string | identifier - The identifier or UUID of the domain object.
* @returns {Promise<Object>} An object containing the properties of the domain object.
*/
async function getDomainObject(page, identifier) {
const domainObject = await page.evaluate(async (objIdentifier) => {
const object = await window.openmct.objects.get(objIdentifier);
return object;
}, identifier);

return domainObject;
}

/**
* Generate a notification with the given options.
* @param {import('@playwright/test').Page} page
Expand Down Expand Up @@ -716,6 +732,7 @@ export {
createStableStateTelemetry,
expandEntireTree,
getCanvasPixels,
getDomainObject,
linkParameterToObject,
navigateToObjectWithFixedTimeBounds,
navigateToObjectWithRealTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import { fileURLToPath } from 'url';

import {
createDomainObjectWithDefaults,
createExampleTelemetryObject
createExampleTelemetryObject,
getDomainObject
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';

Expand Down Expand Up @@ -468,6 +469,34 @@ test.describe('Basic Condition Set Use', () => {
description: 'https://github.com/nasa/openmct/issues/7484'
});
});

test('should toggle shouldFetchHistorical property in inspector', async ({ page }) => {
await page.goto(conditionSet.url);
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click();
let toggleSwitch = page.getByLabel('condition-historical-toggle');
const initialState = await toggleSwitch.isChecked();
expect(initialState).toBe(false);

await toggleSwitch.click();
let toggledState = await toggleSwitch.isChecked();
expect(toggledState).toBe(true);
await page.click('button[title="Save"]');
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
let conditionSetObject = await getDomainObject(page, conditionSet.uuid);
expect(conditionSetObject.configuration.shouldFetchHistorical).toBe(true);

await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click();
toggleSwitch = page.getByLabel('condition-historical-toggle');
await toggleSwitch.click();
toggledState = await toggleSwitch.isChecked();
expect(toggledState).toBe(false);
await page.click('button[title="Save"]');
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
conditionSetObject = await getDomainObject(page, conditionSet.uuid);
expect(conditionSetObject.configuration.shouldFetchHistorical).toBe(false);
});
});

test.describe('Condition Set Composition', () => {
Expand Down
4 changes: 4 additions & 0 deletions karma.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ module.exports = async (config) => {
{
pattern: 'dist/generatorWorker.js*',
included: false
},
{
pattern: 'dist/historicalTelemetryWorker.js*',
included: false
}
],
port: 9876,
Expand Down
73 changes: 73 additions & 0 deletions src/plugins/condition/ConditionInspectorViewProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, 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 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();
}
}
};
}
};
}
158 changes: 137 additions & 21 deletions src/plugins/condition/ConditionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { EventEmitter } from 'eventemitter3';
import { v4 as uuid } from 'uuid';

import Condition from './Condition.js';
import HistoricalTelemetryProvider from './HistoricalTelemetryProvider.js';
import { TELEMETRY_VALUE } from './utils/constants.js';
import { getLatestTimestamp } from './utils/time.js';

export default class ConditionManager extends EventEmitter {
Expand Down Expand Up @@ -52,6 +54,8 @@ export default class ConditionManager extends EventEmitter {
applied: false
};
this.initialize();
this.telemetryBuffer = [];
this.isProcessing = false;
}

subscribeToTelemetry(telemetryObject) {
Expand Down Expand Up @@ -310,6 +314,39 @@ export default class ConditionManager extends EventEmitter {
this.persistConditions();
}

getCurrentCondition() {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length - 1];

for (let i = 0; i < conditionCollection.length - 1; i++) {
const condition = this.findConditionById(conditionCollection[i].id);
if (condition.result) {
//first condition to be true wins
currentCondition = conditionCollection[i];
break;
}
}

return currentCondition;
}

getHistoricalData(options) {
if (!this.conditionSetDomainObject.configuration.shouldFetchHistorical) {
return [];
}
let historicalTelemetry = new HistoricalTelemetryProvider(
this.openmct,
this.telemetryObjects,
this.conditions,
this.conditionSetDomainObject,
options
);
const historicalData = historicalTelemetry.getHistoricalData();
historicalTelemetry = null;

return historicalData;
}

getCurrentConditionLAD(conditionResults) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length - 1];
Expand Down Expand Up @@ -365,14 +402,36 @@ 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];
}
}

let result = currentCondition?.isDefault ? false : conditionResults[currentCondition.id];
const currentOutput = {
output: currentCondition.configuration.output,
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id,
...latestTimestamp
id: this.conditionSetDomainObject.identifier,
output: output,
...latestTimestamp,
result,
isDefault: currentCondition?.isDefault
};

return [currentOutput];
return output !== undefined ? [currentOutput] : [];
}

isTelemetryUsed(endpoint) {
Expand Down Expand Up @@ -407,8 +466,8 @@ export default class ConditionManager extends EventEmitter {
this.#latestDataTable.set(normalizedDatum.id, normalizedDatum);

if (this.shouldEvaluateNewTelemetry(currentTimestamp)) {
const matchingCondition = this.updateConditionResults(normalizedDatum.id);
this.updateCurrentCondition(timestamp, matchingCondition);
this.updateConditionResults(normalizedDatum);
this.updateCurrentCondition(timestamp, endpoint, datum);
}
}

Expand All @@ -423,23 +482,80 @@ export default class ConditionManager extends EventEmitter {
return matchingCondition;
}

updateCurrentCondition(timestamp, matchingCondition) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
const defaultCondition = conditionCollection[conditionCollection.length - 1];
emitConditionSetResult(currentCondition, timestamp, outputValue, result, isDefault) {
this.emit('conditionSetResultUpdated', {
conditionId: currentCondition.id,
id: this.conditionSetDomainObject.identifier,
output: outputValue,
...timestamp,
result,
isDefault
});
}

const currentCondition = matchingCondition || defaultCondition;
updateCurrentCondition(timestamp, telemetryObject, telemetryData) {
this.telemetryBuffer.push({ timestamp, telemetryObject, telemetryData });

this.emit(
'conditionSetResultUpdated',
Object.assign(
{
output: currentCondition.configuration.output,
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
timestamp
)
);
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();
const conditionDetails = this.conditions.filter(
(condition) => condition.id === currentCondition.id
)?.[0];
const conditionResult = currentCondition?.isDefault ? false : conditionDetails?.result;
let telemetryValue = currentCondition.configuration.output;

if (telemetryValue !== undefined && 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,
conditionResult,
currentCondition?.isDefault
);
}
}

getTestData(metadatum, identifier) {
Expand Down
Loading