Compare commits

...

7 Commits

Author SHA1 Message Date
0bf37fc492 chore: bump version to 3.1.1 (#7238) 2023-11-14 14:57:33 -08:00
d40204efe3 cherry-pick(#7203): docs: add warning about deploying devServer to prod environment (#7237)
* docs: add warning about using dev server in
production environment

* docs: fix formatting
2023-11-14 11:33:25 -08:00
ff8288d10e cherry-pick(#7144): fix(#7143): add eslint-plugin-no-unsanitized and fix errors (#7148) 2023-10-23 09:03:24 -07:00
c1dd82cf5f cherry-pick(#7121): Do not store state in singleton action (#7134)
do not store state in singleton action (#7121)

Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
2023-10-10 16:17:36 -07:00
e43ddf6a4a cherry-pick(#7122): [Staleness] Fix issue with object view staleness styles not being res… (#7123)
[Staleness] Fix issue with object view staleness styles not being reset on clock change (#7122)

Add logic to un/re-subscribe when clock changes to object view
2023-10-06 16:37:09 -07:00
018a64eb86 chore: bump version to 3.1.0 (#7118) 2023-10-05 15:48:05 -05:00
b1785da838 cherry-pick(#7088): [Staleness] Fix staleness on clock change (#7112)
[Staleness] Fix staleness on clock change (#7088)

* Update staleness mixin
* Fix listeners and add guard
* Add check to make sure staleness only shows for correct clock
* Add guard for time api
* Cleanup the setting of isStale in ObjectView
* Cleanup use of combinedKey on LadTableSet
2023-10-04 14:29:54 -07:00
31 changed files with 487 additions and 481 deletions

View File

@ -15,7 +15,8 @@ module.exports = {
'plugin:compat/recommended',
'plugin:vue/vue3-recommended',
'plugin:you-dont-need-lodash-underscore/compatible',
'plugin:prettier/recommended'
'plugin:prettier/recommended',
'plugin:no-unsanitized/DOM'
],
parser: 'vue-eslint-parser',
parserOptions: {

3
API.md
View File

@ -94,6 +94,9 @@ well as assets such as html, css, and images necessary for the UI.
## Starting an Open MCT application
> [!WARNING]
> Open MCT provides a development server via `webpack-dev-server` (`npm start`). **This should be used for development purposes only and should never be deployed to a production environment**.
To start a minimally functional Open MCT application, it is necessary to
include the Open MCT distributable, enable some basic plugins, and bootstrap
the application. The tutorials walk through the process of getting Open MCT up

View File

@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "3.1.0-next",
"version": "3.1.1",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.22.5",
@ -27,6 +27,7 @@
"eslint": "8.48.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-compat": "4.2.0",
"eslint-plugin-no-unsanitized": "4.0.2",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-simple-import-sort": "10.0.0",

View File

@ -50,15 +50,14 @@
<script>
import Vue, { toRaw } from 'vue';
import StalenessUtils from '@/utils/staleness';
import LadRow from './LadRow.vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
export default {
components: {
LadRow
},
mixins: [stalenessMixin],
inject: ['openmct', 'currentView', 'ladTableConfiguration'],
props: {
domainObject: {
@ -74,7 +73,6 @@ export default {
return {
items: [],
viewContext: {},
staleObjects: [],
configuration: this.ladTableConfiguration.getConfiguration()
};
},
@ -96,11 +94,7 @@ export default {
return !this.configuration?.hiddenColumns?.type;
},
staleClass() {
if (this.staleObjects.length !== 0) {
return 'is-stale';
}
return '';
return this.isStale ? 'is-stale' : '';
},
applyLayoutClass() {
if (this.configuration.isFixedLayout) {
@ -133,12 +127,16 @@ export default {
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.reorder);
this.composition.load();
this.stalenessSubscription = {};
await Vue.nextTick();
this.viewActionsCollection = this.openmct.actions.getActionsCollection(
this.objectPath,
this.currentView
);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
this.initializeViewActions();
},
unmounted() {
@ -147,11 +145,6 @@ export default {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
},
methods: {
addItem(domainObject) {
@ -160,35 +153,16 @@ export default {
item.key = this.openmct.objects.makeKeyString(domainObject.identifier);
this.items.push(item);
this.stalenessSubscription[item.key] = {};
this.stalenessSubscription[item.key].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(item.key, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(item.key, stalenessResponse);
}
);
this.stalenessSubscription[item.key].unsubscribe = stalenessSubscription;
this.subscribeToStaleness(domainObject);
},
removeItem(identifier) {
const SKIP_CHECK = true;
const keystring = this.openmct.objects.makeKeyString(identifier);
const index = this.items.findIndex((item) => keystring === item.key);
const index = this.items.findIndex((item) => keystring === item.key);
this.items.splice(index, 1);
this.stalenessSubscription[keystring].unsubscribe();
this.handleStaleness(keystring, { isStale: false }, SKIP_CHECK);
const domainObject = this.openmct.objects.get(keystring);
this.triggerUnsubscribeFromStaleness(domainObject);
},
reorder(reorderPlan) {
const oldItems = this.items.slice();
@ -204,23 +178,6 @@ export default {
handleConfigurationChange(configuration) {
this.configuration = configuration;
},
handleStaleness(id, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[id].stalenessUtils.shouldUpdateStaleness(stalenessResponse)
) {
const index = this.staleObjects.indexOf(id);
if (stalenessResponse.isStale) {
if (index === -1) {
this.staleObjects.push(id);
}
} else {
if (index !== -1) {
this.staleObjects.splice(index, 1);
}
}
}
},
updateViewContext(rowContext) {
this.viewContext.row = rowContext;
},

View File

@ -45,9 +45,9 @@
:domain-object="ladRow.domainObject"
:path-to-table="ladTable.objectPath"
:has-units="hasUnits"
:is-stale="staleObjects.includes(combineKeys(ladTable.key, ladRow.key))"
:is-stale="staleObjects.includes(ladRow.key)"
:configuration="configuration"
@rowContextClick="updateViewContext"
@row-context-click="updateViewContext"
/>
</template>
</tbody>
@ -56,7 +56,9 @@
</template>
<script>
import StalenessUtils from '@/utils/staleness';
import { toRaw } from 'vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
import LadRow from './LadRow.vue';
@ -64,6 +66,7 @@ export default {
components: {
LadRow
},
mixins: [stalenessMixin],
inject: ['openmct', 'objectPath', 'currentView', 'ladTableConfiguration'],
props: {
domainObject: {
@ -76,8 +79,8 @@ export default {
ladTableObjects: [],
ladTelemetryObjects: {},
viewContext: {},
staleObjects: [],
configuration: this.ladTableConfiguration.getConfiguration()
configuration: this.ladTableConfiguration.getConfiguration(),
subscribedObjects: {}
};
},
computed: {
@ -108,11 +111,7 @@ export default {
return !this.configuration?.hiddenColumns?.type;
},
staleClass() {
if (this.staleObjects.length !== 0) {
return 'is-stale';
}
return '';
return this.isStale ? 'is-stale' : '';
}
},
created() {
@ -125,8 +124,10 @@ export default {
this.composition.on('remove', this.removeLadTable);
this.composition.on('reorder', this.reorderLadTables);
this.composition.load();
this.stalenessSubscription = {};
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
unmounted() {
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
@ -137,11 +138,6 @@ export default {
c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback);
});
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
},
methods: {
addLadTable(domainObject) {
@ -176,9 +172,18 @@ export default {
);
let ladTable = this.ladTableObjects[index];
this.ladTelemetryObjects[ladTable.key].forEach((telemetryObject) => {
let combinedKey = this.combineKeys(ladTable.key, telemetryObject.key);
this.unwatchStaleness(combinedKey);
ladTable?.domainObject?.composition.forEach((telemetryObject) => {
const telemetryKey = this.openmct.objects.makeKeyString(telemetryObject);
if (!this.subscribedObjects?.[telemetryKey]) {
return;
}
let subscribedObject = toRaw(this.subscribedObjects[telemetryKey]);
if (subscribedObject?.count > 1) {
subscribedObject.count -= 1;
} else if (subscribedObject?.count === 1) {
this.triggerUnsubscribeFromStaleness(subscribedObject.domainObject);
delete this.subscribedObjects[telemetryKey];
}
});
delete this.ladTelemetryObjects[ladTable.key];
@ -199,77 +204,35 @@ export default {
let telemetryObject = {};
telemetryObject.key = this.openmct.objects.makeKeyString(domainObject.identifier);
telemetryObject.domainObject = domainObject;
const combinedKey = this.combineKeys(ladTable.key, telemetryObject.key);
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
telemetryObjects.push(telemetryObject);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.stalenessSubscription[combinedKey] = {};
this.stalenessSubscription[combinedKey].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(combinedKey, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(combinedKey, stalenessResponse);
}
);
this.stalenessSubscription[combinedKey].unsubscribe = stalenessSubscription;
if (!this.subscribedObjects[telemetryObject?.key]) {
this.subscribeToStaleness(domainObject);
this.subscribedObjects[telemetryObject?.key] = { count: 1, domainObject };
} else if (this.subscribedObjects?.[telemetryObject?.key]?.count) {
this.subscribedObjects[telemetryObject?.key].count += 1;
}
};
},
removeTelemetryObject(ladTable) {
return (identifier) => {
const keystring = this.openmct.objects.makeKeyString(identifier);
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
const combinedKey = this.combineKeys(ladTable.key, keystring);
let index = telemetryObjects.findIndex(
(telemetryObject) => keystring === telemetryObject.key
);
this.unwatchStaleness(combinedKey);
telemetryObjects.splice(index, 1);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
};
},
unwatchStaleness(combinedKey) {
const SKIP_CHECK = true;
this.stalenessSubscription[combinedKey].unsubscribe();
this.stalenessSubscription[combinedKey].stalenessUtils.destroy();
this.handleStaleness(combinedKey, { isStale: false }, SKIP_CHECK);
delete this.stalenessSubscription[combinedKey];
},
handleConfigurationChange(configuration) {
this.configuration = configuration;
},
handleStaleness(combinedKey, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[combinedKey].stalenessUtils.shouldUpdateStaleness(
stalenessResponse
)
) {
const index = this.staleObjects.indexOf(combinedKey);
const foundStaleObject = index > -1;
if (stalenessResponse.isStale && !foundStaleObject) {
this.staleObjects.push(combinedKey);
} else if (!stalenessResponse.isStale && foundStaleObject) {
this.staleObjects.splice(index, 1);
}
}
},
updateViewContext(rowContext) {
this.viewContext.row = rowContext;
},

View File

@ -97,8 +97,10 @@ describe('The condition', function () {
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems', 'on', 'off']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
openmct.time.on.and.returnValue(() => {});
openmct.time.off.and.returnValue(() => {});
testConditionDefinition = {
id: '123-456',

View File

@ -79,15 +79,15 @@
</template>
<script>
import StalenessUtils from '@/utils/staleness';
import ConditionManager from '../ConditionManager';
import Condition from './Condition.vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
export default {
components: {
Condition
},
mixins: [stalenessMixin],
inject: ['openmct', 'domainObject'],
props: {
isEditing: Boolean,
@ -135,13 +135,6 @@ export default {
if (this.stopObservingForChanges) {
this.stopObservingForChanges();
}
if (this.stalenessSubscription) {
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
}
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
@ -153,7 +146,20 @@ export default {
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
this.conditionManager.on('noTelemetryObjects', this.emitNoTelemetryObjectEvent);
this.stalenessSubscription = {};
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject, () => {
this.emitStaleness({
keyString: domainObject.identifier,
stalenessResponse: { isStale: false }
});
});
this.subscribeToStaleness(domainObject, (stalenessResponse) => {
this.emitStaleness({
keyString: domainObject.identifier,
stalenessResponse: stalenessResponse
});
});
});
},
methods: {
handleConditionSetResultUpdated(data) {
@ -221,28 +227,12 @@ export default {
this.telemetryObjs.push(domainObject);
this.$emit('telemetryUpdated', this.telemetryObjs);
if (!this.stalenessSubscription[keyString]) {
this.stalenessSubscription[keyString] = {};
}
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
this.subscribeToStaleness(domainObject, (stalenessResponse) => {
this.emitStaleness({
keyString,
stalenessResponse: stalenessResponse
});
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
},
removeTelemetryObject(identifier) {
const keyString = this.openmct.objects.makeKeyString(identifier);
@ -252,31 +242,17 @@ export default {
return objId === keyString;
});
const domainObject = this.telemetryObjs[index];
this.triggerUnsubscribeFromStaleness(domainObject, () => {
this.emitStaleness({
keyString,
stalenessResponse: { isStale: false }
});
});
if (index > -1) {
this.telemetryObjs.splice(index, 1);
}
if (this.stalenessSubscription[keyString]) {
this.stalenessSubscription[keyString].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.emitStaleness({
keyString,
isStale: false
});
delete this.stalenessSubscription[keyString];
}
},
handleStaleness(keyString, stalenessResponse) {
if (
this.stalenessSubscription[keyString].stalenessUtils.shouldUpdateStaleness(
stalenessResponse
)
) {
this.emitStaleness({
keyString,
isStale: stalenessResponse.isStale
});
}
},
emitStaleness(stalenessObject) {
this.$emit('telemetryStaleness', stalenessObject);

View File

@ -57,12 +57,14 @@
<script>
import ConditionCollection from './ConditionCollection.vue';
import TestData from './TestData.vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
export default {
components: {
TestData,
ConditionCollection
},
mixins: [stalenessMixin],
inject: ['openmct', 'domainObject'],
props: {
isEditing: Boolean
@ -71,15 +73,9 @@ export default {
return {
currentConditionOutput: '',
telemetryObjs: [],
testData: {},
staleObjects: []
testData: {}
};
},
computed: {
isStale() {
return this.staleObjects.length !== 0;
}
},
mounted() {
this.conditionSetIdentifier = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.testData = {
@ -100,17 +96,8 @@ export default {
updateTestData(testData) {
this.testData = testData;
},
handleStaleness({ keyString, isStale }) {
const index = this.staleObjects.indexOf(keyString);
if (isStale) {
if (index === -1) {
this.staleObjects.push(keyString);
}
} else {
if (index !== -1) {
this.staleObjects.splice(index, 1);
}
}
handleStaleness({ keyString, stalenessResponse }) {
this.addOrRemoveStaleObject(keyString, stalenessResponse);
}
}
};

View File

@ -52,6 +52,8 @@ export default class TelemetryCriterion extends EventEmitter {
this.initialize();
this.emitEvent('criterionUpdated', this);
this.openmct.time.on('clockChanged', this.subscribeToStaleness);
}
initialize() {
@ -95,6 +97,10 @@ export default class TelemetryCriterion extends EventEmitter {
this.unsubscribeFromStaleness();
}
if (!this.telemetryObject) {
return;
}
if (!this.stalenessUtils) {
this.stalenessUtils = new StalenessUtils(this.openmct, this.telemetryObject);
}
@ -333,6 +339,8 @@ export default class TelemetryCriterion extends EventEmitter {
delete this.ageCheck;
}
this.openmct.time.off('clockChanged', this.subscribeToStaleness);
if (this.stalenessUtils) {
this.stalenessUtils.destroy();
}

View File

@ -88,7 +88,9 @@ describe('The telemetry criterion', function () {
'timeSystem',
'bounds',
'getAllTimeSystems',
'getContextForView'
'getContextForView',
'on',
'off'
]);
openmct.time.timeSystem.and.returnValue({ key: 'system' });
openmct.time.bounds.and.returnValue({
@ -97,6 +99,8 @@ describe('The telemetry criterion', function () {
});
openmct.time.getAllTimeSystems.and.returnValue([{ key: 'system' }]);
openmct.time.getContextForView.and.returnValue({});
openmct.time.on.and.returnValue(() => {});
openmct.time.off.and.returnValue(() => {});
testCriterionDefinition = {
id: 'test-criterion-id',

View File

@ -240,6 +240,11 @@ export default {
this.status = this.openmct.status.get(this.item.identifier);
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
beforeUnmount() {
this.removeStatusListener();

View File

@ -161,8 +161,11 @@ export default {
let originalClassName = this.dragGhost.classList[0];
this.dragGhost.className = '';
this.dragGhost.classList.add(originalClassName, iconClass);
this.dragGhost.textContent = '';
const span = document.createElement('span');
span.textContent = this.domainObject.name;
this.dragGhost.appendChild(span);
this.dragGhost.innerHTML = `<span>${this.domainObject.name}</span>`;
event.dataTransfer.setDragImage(this.dragGhost, 0, 0);
}

View File

@ -541,6 +541,11 @@ export default {
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
unmounted() {
this.composition.off('add', this.addedToComposition);
@ -620,7 +625,7 @@ export default {
this.unsubscribe = null;
}
this.triggerUnsubscribeFromStaleness();
this.triggerUnsubscribeFromStaleness(this.domainObject);
this.curVal = DEFAULT_CURRENT_VALUE;
this.formats = null;

View File

@ -32,7 +32,6 @@ export default class ImportAsJSONAction {
this.cssClass = 'icon-import';
this.group = 'import';
this.priority = 2;
this.newObjects = [];
this.openmct = openmct;
}
@ -83,9 +82,10 @@ export default class ImportAsJSONAction {
* @param {object} parent
* @param {object} tree
* @param {object} seen
* @param {Array} objectsToCreate tracks objects from import json that will need to be created
*/
_deepInstantiate(parent, tree, seen) {
let objectIdentifiers = this._getObjectReferenceIds(parent);
_deepInstantiate(parent, tree, seen, objectsToCreate) {
const objectIdentifiers = this._getObjectReferenceIds(parent);
if (objectIdentifiers.length) {
const parentId = this.openmct.objects.makeKeyString(parent.identifier);
@ -100,15 +100,16 @@ export default class ImportAsJSONAction {
const newModel = tree[keystring];
delete newModel.persisted;
this.newObjects.push(newModel);
objectsToCreate.push(newModel);
// make sure there weren't any errors saving
if (newModel) {
this._deepInstantiate(newModel, tree, seen);
this._deepInstantiate(newModel, tree, seen, objectsToCreate);
}
}
}
}
/**
* @private
* @param {object} parent
@ -170,19 +171,19 @@ export default class ImportAsJSONAction {
* @param {object} objTree
*/
async _importObjectTree(domainObject, objTree) {
const objectsToCreate = [];
const namespace = domainObject.identifier.namespace;
const tree = this._generateNewIdentifiers(objTree, namespace);
const rootId = tree.rootId;
const rootObj = tree.openmct[rootId];
delete rootObj.persisted;
this.newObjects.push(rootObj);
objectsToCreate.push(rootObj);
if (this.openmct.composition.checkPolicy(domainObject, rootObj)) {
this._deepInstantiate(rootObj, tree.openmct, []);
this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate);
try {
await Promise.all(this.newObjects.map(this._instantiate, this));
await Promise.all(objectsToCreate.map(this._instantiate, this));
} catch (error) {
this.openmct.notifications.error('Error saving objects');

View File

@ -62,13 +62,12 @@
</template>
<script>
import StalenessUtils from '@/utils/staleness';
import ImageExporter from '../../exporters/ImageExporter';
import ProgressBar from '../../ui/components/ProgressBar.vue';
import PlotLegend from './legend/PlotLegend.vue';
import eventHelpers from './lib/eventHelpers';
import MctPlot from './MctPlot.vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
export default {
components: {
@ -76,6 +75,7 @@ export default {
ProgressBar,
PlotLegend
},
mixins: [stalenessMixin],
inject: ['openmct', 'domainObject', 'path'],
props: {
options: {
@ -131,7 +131,6 @@ export default {
return {
loading: false,
status: '',
staleObjects: [],
limitLineLabels: undefined,
lockHighlightPoint: false,
highlights: [],
@ -148,11 +147,7 @@ export default {
return this.gridLines ?? !this.options.compact;
},
staleClass() {
if (this.staleObjects.length !== 0) {
return 'is-stale';
}
return '';
return this.isStale ? 'is-stale' : '';
},
plotLegendPositionClass() {
return this.position ? `plot-legend-${this.position}` : '';
@ -176,8 +171,11 @@ export default {
created() {
eventHelpers.extend(this);
this.imageExporter = new ImageExporter(this.openmct);
this.stalenessSubscription = {};
this.loadComposition();
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
unmounted() {
this.destroy();
@ -187,76 +185,19 @@ export default {
this.compositionCollection = this.openmct.composition.get(this.domainObject);
if (this.compositionCollection) {
this.compositionCollection.on('add', this.addItem);
this.compositionCollection.on('remove', this.removeItem);
this.compositionCollection.on('add', this.subscribeToStaleness);
this.compositionCollection.on('remove', this.triggerUnsubscribeFromStaleness);
this.compositionCollection.load();
}
},
addItem(object) {
const keystring = this.openmct.objects.makeKeyString(object.identifier);
if (!this.stalenessSubscription[keystring]) {
this.stalenessSubscription[keystring] = {};
this.stalenessSubscription[keystring].stalenessUtils = new StalenessUtils(
this.openmct,
object
);
}
this.openmct.telemetry.isStale(object).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keystring, stalenessResponse);
}
});
const unsubscribeFromStaleness = this.openmct.telemetry.subscribeToStaleness(
object,
(stalenessResponse) => {
this.handleStaleness(keystring, stalenessResponse);
}
);
this.stalenessSubscription[keystring].unsubscribe = unsubscribeFromStaleness;
},
removeItem(object) {
const SKIP_CHECK = true;
const keystring = this.openmct.objects.makeKeyString(object);
this.stalenessSubscription[keystring].unsubscribe();
this.stalenessSubscription[keystring].stalenessUtils.destroy();
this.handleStaleness(keystring, { isStale: false }, SKIP_CHECK);
delete this.stalenessSubscription[keystring];
},
handleStaleness(id, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[id].stalenessUtils.shouldUpdateStaleness(stalenessResponse, id)
) {
const index = this.staleObjects.indexOf(id);
if (stalenessResponse.isStale) {
if (index === -1) {
this.staleObjects.push(id);
}
} else {
if (index !== -1) {
this.staleObjects.splice(index, 1);
}
}
}
},
loadingUpdated(loading) {
this.loading = loading;
this.$emit('loadingUpdated', ...arguments);
},
destroy() {
if (this.stalenessSubscription) {
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
}
if (this.compositionCollection) {
this.compositionCollection.off('add', this.addItem);
this.compositionCollection.off('remove', this.removeItem);
this.compositionCollection.off('add', this.subscribeToStaleness);
this.compositionCollection.off('remove', this.triggerUnsubscribeFromStaleness);
}
this.imageExporter = null;

View File

@ -20,12 +20,10 @@
at runtime from the About dialog for additional information.
-->
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span>
<span v-html="canvasTemplate"></span>
<canvas :style="canvasStyle"></canvas>
<canvas :style="canvasStyle"></canvas>
<div ref="limitArea" class="js-limit-area">
<limit-label
v-for="(limitLabel, index) in visibleLimitLabels"
@ -146,12 +144,20 @@ export default {
},
data() {
return {
canvasTemplate:
'<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>',
visibleLimitLabels: [],
visibleLimitLines: []
};
},
computed: {
canvasStyle() {
return {
position: 'absolute',
background: 'none',
width: '100%',
height: '100%'
};
}
},
watch: {
highlights: {
handler() {
@ -487,7 +493,10 @@ export default {
// Have to throw away the old canvas elements and replace with new
// canvas elements in order to get new drawing contexts.
const div = document.createElement('div');
div.innerHTML = this.canvasTemplate + this.canvasTemplate;
div.innerHTML = `
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
`;
const mainCanvas = div.querySelectorAll('canvas')[1];
const overlayCanvas = div.querySelectorAll('canvas')[0];
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);

View File

@ -127,6 +127,10 @@ export default {
this.config.series.forEach(this.onSeriesAdd, this);
this.legend = this.config.legend;
this.loaded = true;
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
beforeUnmount() {
this.stopListening();

View File

@ -150,6 +150,10 @@ export default {
this.config.series.forEach(this.onSeriesAdd, this);
this.legend = this.config.legend;
this.loaded = true;
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
beforeUnmount() {
this.stopListening();

View File

@ -28,7 +28,6 @@ import mount from 'utils/mount';
import configStore from '@/plugins/plot/configuration/ConfigStore';
import PlotConfigurationModel from '@/plugins/plot/configuration/PlotConfigurationModel';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
import StalenessUtils from '@/utils/staleness';
import Plot from '../Plot.vue';
import conditionalStylesMixin from './mixins/objectStyles-mixin';
@ -90,11 +89,6 @@ export default {
}
}
},
data() {
return {
staleObjects: []
};
},
watch: {
gridLines(newGridLines) {
this.updateComponentProp('gridLines', newGridLines);
@ -116,20 +110,26 @@ export default {
},
staleObjects: {
handler() {
this.isStale = this.staleObjects.length > 0;
this.updateComponentProp('isStale', this.isStale);
},
deep: true
}
},
mounted() {
this.stalenessSubscription = {};
this.updateView();
this.isEditing = this.openmct.editor.isEditing();
this.openmct.editor.on('isEditing', this.setEditState);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
beforeUnmount() {
this.openmct.editor.off('isEditing', this.setEditState);
if (this.composition) {
this.composition.off('add', this.subscribeToStaleness);
this.composition.off('remove', this.triggerUnsubscribeFromStaleness);
}
if (this.removeSelectable) {
this.removeSelectable();
@ -141,8 +141,6 @@ export default {
if (this._destroy) {
this._destroy();
}
this.destroyStalenessListeners();
},
methods: {
setEditState(isEditing) {
@ -163,10 +161,6 @@ export default {
}
},
updateView() {
this.isStale = false;
this.destroyStalenessListeners();
if (this._destroy) {
this._destroy();
this.component = null;
@ -190,15 +184,15 @@ export default {
const isMissing = openmct.objects.isMissing(object);
if (this.openmct.telemetry.isTelemetryObject(object)) {
this.subscribeToStaleness(object, (isStale) => {
this.updateComponentProp('isStale', isStale);
this.subscribeToStaleness(object, (stalenessResponse) => {
this.updateComponentProp('isStale', stalenessResponse.isStale);
});
} else {
// possibly overlay or other composition based plot
this.composition = this.openmct.composition.get(object);
this.composition.on('add', this.watchStaleness);
this.composition.on('remove', this.unwatchStaleness);
this.composition.on('add', this.subscribeToStaleness);
this.composition.on('remove', this.triggerUnsubscribeFromStaleness);
this.composition.load();
}
@ -260,54 +254,6 @@ export default {
this.setSelection();
}
},
watchStaleness(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.stalenessSubscription[keyString] = {};
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
},
unwatchStaleness(domainObject) {
const SKIP_CHECK = true;
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.stalenessSubscription[keyString].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
delete this.stalenessSubscription[keyString];
},
handleStaleness(keyString, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[keyString].stalenessUtils.shouldUpdateStaleness(
stalenessResponse
)
) {
const index = this.staleObjects.indexOf(keyString);
const foundStaleObject = index > -1;
if (stalenessResponse.isStale && !foundStaleObject) {
this.staleObjects.push(keyString);
} else if (!stalenessResponse.isStale && foundStaleObject) {
this.staleObjects.splice(index, 1);
}
}
},
onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments);
},
@ -405,20 +351,6 @@ export default {
return this.childObject;
}
},
destroyStalenessListeners() {
this.triggerUnsubscribeFromStaleness();
if (this.composition) {
this.composition.off('add', this.watchStaleness);
this.composition.off('remove', this.unwatchStaleness);
this.composition = null;
}
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
}
}
};

View File

@ -214,7 +214,7 @@ define([
const options = this.generateSelectOptions();
newInput = document.createElement('select');
newInput.innerHTML = options;
newInput.appendChild(options);
emitChange = true;
} else {
@ -244,12 +244,16 @@ define([
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let options = '';
let fragment = document.createDocumentFragment();
telemetryMetadata[this.config.key].enumerations.forEach((enumeration) => {
options += '<option value="' + enumeration.value + '">' + enumeration.string + '</option>';
const option = document.createElement('option');
option.value = enumeration.value;
option.textContent = enumeration.string;
fragment.appendChild(option);
});
return options;
return fragment;
};
return Condition;

View File

@ -167,7 +167,8 @@ define([
const ruleHeader = self.domElement
.querySelectorAll('.widget-rule-header')[0]
.cloneNode(true);
indicator.innerHTML = ruleHeader;
indicator.textContent = '';
indicator.appendChild(ruleHeader);
});
self.widgetDnD.setDragImage(
self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true)
@ -239,8 +240,8 @@ define([
this.listenTo(this.toggleConfigButton, 'click', toggleConfig);
this.listenTo(this.trigger, 'change', onTriggerInput);
this.title.innerHTML = self.config.name;
this.description.innerHTML = self.config.description;
this.title.innerText = self.config.name;
this.description.innerText = self.config.description;
this.trigger.value = self.config.trigger;
this.listenTo(this.grippy, 'mousedown', onDragStart);
@ -456,7 +457,7 @@ define([
const lastOfType = self.conditionArea.querySelector('li:last-of-type');
lastOfType.parentNode.insertBefore($condition, lastOfType);
if (loopCnt > 0) {
$condition.querySelector('.t-condition-context').innerHTML = triggerContextStr + ' when';
$condition.querySelector('.t-condition-context').innerText = triggerContextStr + ' when';
}
loopCnt++;
@ -528,7 +529,7 @@ define([
}
description = description === '' ? this.config.description : description;
this.description.innerHTML = self.config.description;
this.description.innerText = self.config.description;
this.config.description = description;
};

View File

@ -247,9 +247,10 @@ define([
SummaryWidget.prototype.updateWidget = function () {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
const activeRule = this.rulesById[this.activeId];
this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style'));
this.domElement.querySelector('#widget').title = activeRule.getProperty('message');
this.domElement.querySelector('#widgetLabel').innerHTML = activeRule.getProperty('label');
this.domElement.querySelector('#widgetLabel').textContent = activeRule.getProperty('label');
this.domElement.querySelector('#widgetIcon').classList =
WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon');
};

View File

@ -44,11 +44,12 @@ define([
self.setNullOption(this.nullOption);
self.items.forEach(function (item) {
const itemElement = `<div class = "c-palette__item ${item}" data-item = "${item}"></div>`;
const temp = document.createElement('div');
temp.innerHTML = itemElement;
self.itemElements[item] = temp.firstChild;
self.domElement.querySelector('.c-palette__items').appendChild(temp.firstChild);
const itemElement = document.createElement('div');
itemElement.className = 'c-palette__item ' + item;
itemElement.setAttribute('data-item', item);
self.itemElements[item] = itemElement;
self.domElement.querySelector('.c-palette__items').appendChild(itemElement);
});
self.domElement.querySelector('.c-menu').style.display = 'none';

View File

@ -1,35 +1,60 @@
define(['./summary-widget.html', '@braintree/sanitize-url'], function (
summaryWidgetTemplate,
urlSanitizeLib
) {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
import * as urlSanitizeLib from '@braintree/sanitize-url';
function SummaryWidgetView(domainObject, openmct) {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
class SummaryWidgetView {
#createSummaryWidgetTemplate() {
const anchor = document.createElement('a');
anchor.classList.add(
't-summary-widget',
'c-summary-widget',
'js-sw',
'u-links',
'u-fills-container'
);
const widgetIcon = document.createElement('div');
widgetIcon.id = 'widgetIcon';
widgetIcon.classList.add('c-sw__icon', 'js-sw__icon');
anchor.appendChild(widgetIcon);
const widgetLabel = document.createElement('div');
widgetLabel.id = 'widgetLabel';
widgetLabel.classList.add('c-sw__label', 'js-sw__label');
widgetLabel.textContent = 'Loading...';
anchor.appendChild(widgetLabel);
return anchor;
}
constructor(domainObject, openmct) {
this.openmct = openmct;
this.domainObject = domainObject;
this.hasUpdated = false;
this.render = this.render.bind(this);
}
SummaryWidgetView.prototype.updateState = function (datum) {
updateState(datum) {
this.hasUpdated = true;
this.widget.style.color = datum.textColor;
this.widget.style.backgroundColor = datum.backgroundColor;
this.widget.style.borderColor = datum.borderColor;
this.widget.title = datum.message;
this.label.title = datum.message;
this.label.innerHTML = datum.ruleLabel;
this.label.textContent = datum.ruleLabel;
this.icon.className = WIDGET_ICON_CLASS + ' ' + datum.icon;
};
}
SummaryWidgetView.prototype.render = function () {
render() {
if (this.unsubscribe) {
this.unsubscribe();
}
this.hasUpdated = false;
this.container.innerHTML = summaryWidgetTemplate;
const anchor = this.#createSummaryWidgetTemplate();
this.container.appendChild(anchor);
this.widget = this.container.querySelector('a');
this.icon = this.container.querySelector('#widgetIcon');
this.label = this.container.querySelector('.js-sw__label');
@ -49,33 +74,32 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
const renderTracker = {};
this.renderTracker = renderTracker;
this.openmct.telemetry
.request(this.domainObject, {
strategy: 'latest',
size: 1
})
.then(
function (results) {
if (
this.destroyed ||
this.hasUpdated ||
this.renderTracker !== renderTracker ||
results.length === 0
) {
return;
}
.then((results) => {
if (
this.destroyed ||
this.hasUpdated ||
this.renderTracker !== renderTracker ||
results.length === 0
) {
return;
}
this.updateState(results[results.length - 1]);
}.bind(this)
);
this.updateState(results[results.length - 1]);
});
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateState.bind(this)
);
};
}
SummaryWidgetView.prototype.show = function (container) {
show(container) {
this.container = container;
this.render();
this.removeMutationListener = this.openmct.objects.observe(
@ -84,14 +108,14 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
this.onMutation.bind(this)
);
this.openmct.time.on('timeSystem', this.render);
};
}
SummaryWidgetView.prototype.onMutation = function (domainObject) {
onMutation(domainObject) {
this.domainObject = domainObject;
this.render();
};
}
SummaryWidgetView.prototype.destroy = function (container) {
destroy() {
this.unsubscribe();
this.removeMutationListener();
this.openmct.time.off('timeSystem', this.render);
@ -100,7 +124,7 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
delete this.label;
delete this.openmct;
delete this.domainObject;
};
}
}
return SummaryWidgetView;
});
export default SummaryWidgetView;

View File

@ -1,4 +0,0 @@
<a class="t-summary-widget c-summary-widget js-sw u-links u-fills-container">
<div id="widgetIcon" class="c-sw__icon js-sw__icon"></div>
<div id="widgetLabel" class="c-sw__label js-sw__label">Loading...</div>
</a>

View File

@ -55,6 +55,7 @@ define([
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.telemetryObjects = {};
this.subscribedStaleObjects = new Map();
this.telemetryCollections = {};
this.delayedActions = [];
this.outstandingRequests = 0;
@ -74,6 +75,8 @@ define([
this.filterObserver = undefined;
this.createTableRowCollections();
this.resubscribeToStaleness = this.resubscribeAllObjectsToStaleness.bind(this);
this.openmct.time.on('clockChanged', this.resubscribeToStaleness);
}
/**
@ -163,24 +166,7 @@ define([
this.telemetryCollections[keyString].on('clear', this.clearData);
this.telemetryCollections[keyString].load();
this.stalenessSubscription[keyString] = {};
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils.default(
this.openmct,
telemetryObject
);
this.openmct.telemetry.isStale(telemetryObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
telemetryObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
this.subscribeToStaleness(telemetryObject);
this.telemetryObjects[keyString] = {
telemetryObject,
@ -193,6 +179,42 @@ define([
this.emit('object-added', telemetryObject);
}
resubscribeAllObjectsToStaleness() {
if (!this.subscribedStaleObjects || this.subscribedStaleObjects.size < 1) {
return;
}
for (const [, telemetryObject] of this.subscribedStaleObjects) {
this.subscribeToStaleness(telemetryObject);
}
}
subscribeToStaleness(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (this.stalenessSubscription?.[keyString]) {
this.unsubscribeFromStaleness(domainObject.identifier);
}
this.stalenessSubscription[keyString] = {};
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils.default(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.subscribedStaleObjects.set(keyString, domainObject);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
}
handleStaleness(keyString, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
@ -203,7 +225,7 @@ define([
) {
this.emit('telemetry-staleness', {
keyString,
isStale: stalenessResponse.isStale
stalenessResponse: stalenessResponse
});
}
}
@ -310,7 +332,6 @@ define([
removeTelemetryObject(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
const SKIP_CHECK = true;
this.configuration.removeColumnsForObject(objectIdentifier, true);
this.tableRows.removeRowsByObject(keyString);
@ -320,6 +341,13 @@ define([
this.emit('object-removed', objectIdentifier);
this.unsubscribeFromStaleness(objectIdentifier);
}
unsubscribeFromStaleness(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
const SKIP_CHECK = true;
this.stalenessSubscription[keyString].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
@ -423,6 +451,7 @@ define([
this.tableRows.destroy();
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
this.openmct.time.off('clockChanged', this.resubscribeToStaleness);
let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection);

View File

@ -275,6 +275,7 @@
<script>
import _ from 'lodash';
import { toRaw } from 'vue';
import stalenessMixin from '@/ui/mixins/staleness-mixin';
import CSVExporter from '../../../exporters/CSVExporter.js';
import ProgressBar from '../../../ui/components/ProgressBar.vue';
@ -300,6 +301,7 @@ export default {
SizingRow,
ProgressBar
},
mixins: [stalenessMixin],
inject: ['openmct', 'objectPath', 'table', 'currentView'],
props: {
isEditing: {
@ -370,8 +372,7 @@ export default {
enableRegexSearch: {},
hideHeaders: configuration.hideHeaders,
totalNumberOfRows: 0,
rowContext: {},
staleObjects: []
rowContext: {}
};
},
computed: {
@ -414,7 +415,7 @@ export default {
classes.push('is-paused');
}
if (this.staleObjects.length !== 0) {
if (this.isStale) {
classes.push('is-stale');
}
@ -745,17 +746,8 @@ export default {
outstandingRequests(loading) {
this.loading = loading;
},
handleStaleness({ keyString, isStale }) {
const index = this.staleObjects.indexOf(keyString);
if (isStale) {
if (index === -1) {
this.staleObjects.push(keyString);
}
} else {
if (index !== -1) {
this.staleObjects.splice(index, 1);
}
}
handleStaleness({ keyString, stalenessResponse }) {
this.addOrRemoveStaleObject(keyString, stalenessResponse);
},
calculateTableSize() {
this.$nextTick().then(this.calculateColumnWidths);

View File

@ -139,6 +139,10 @@ export default {
this.initObjectStyles();
this.triggerStalenessSubscribe(this.domainObject);
}
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
},
methods: {
clear() {
@ -172,8 +176,7 @@ export default {
this.composition._destroy();
}
this.isStale = false;
this.triggerUnsubscribeFromStaleness();
this.triggerUnsubscribeFromStaleness(this.domainObject);
this.openmct.objectViews.off('clearData', this.clearData);
this.openmct.objectViews.off('contextAction', this.performContextAction);

View File

@ -20,52 +20,190 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { isProxy, toRaw } from 'vue';
import StalenessUtils from '@/utils/staleness';
export default {
data() {
return {
isStale: false
staleObjects: [],
stalenessSubscription: {},
compositionObjectMap: new Map(),
setupClockChanged: false
};
},
beforeUnmount() {
this.triggerUnsubscribeFromStaleness();
computed: {
isStale() {
return this.staleObjects.length !== 0;
}
},
methods: {
subscribeToStaleness(domainObject, callback) {
if (!this.stalenessUtils) {
this.stalenessUtils = new StalenessUtils(this.openmct, domainObject);
getSubscriptionId(domainObject) {
return this.openmct?.objects.makeKeyString(domainObject.identifier);
},
setupClockChangedEvent(callback) {
this.setupClockChanged = true;
this.compositionIteratorCallback = this.compositionIterator(callback);
this.openmct.time.on('clockChanged', this.compositionIteratorCallback);
},
addToCompositionMap(id, domainObject) {
if (!this.compositionObjectMap.get(id)) {
this.compositionObjectMap.set(id, domainObject);
}
},
compositionIterator(callback) {
return () => {
this.staleObjects = [];
for (const [, object] of this.compositionObjectMap) {
let domainObject = object;
if (isProxy(domainObject)) {
domainObject = toRaw(object);
}
if (callback && typeof callback === 'function') {
callback(domainObject);
}
}
};
},
subscribeToStaleness(domainObjectList, callback) {
if (domainObjectList === null || domainObjectList === undefined) {
return;
}
if (!Array.isArray(domainObjectList)) {
domainObjectList = [domainObjectList];
}
this.requestStaleness(domainObject);
this.unsubscribeFromStaleness = this.openmct.telemetry.subscribeToStaleness(
domainObjectList.forEach((domainObject) => {
if (isProxy(domainObject)) {
domainObject = toRaw(domainObject);
}
const id = this.getSubscriptionId(domainObject);
this.addToCompositionMap(id, domainObject);
this.setupStalenessUtils(domainObject);
this.requestStaleness(domainObject, callback);
this.setupStalenessSubscription(domainObject, callback);
});
},
triggerUnsubscribeFromStaleness(domainObjectList, callback) {
if (domainObjectList === null || domainObjectList === undefined) {
return;
}
if (!Array.isArray(domainObjectList)) {
domainObjectList = [domainObjectList];
}
domainObjectList.forEach((domainObject) => {
if (isProxy(domainObject)) {
domainObject = toRaw(domainObject);
}
const id = this.getSubscriptionId(domainObject);
if (!this.stalenessSubscription[id]) {
return;
}
if (this.staleObjects.length !== 0) {
const SKIP_CHECK = true;
this.handleStalenessResponse(id, { isStale: false }, SKIP_CHECK);
}
this.teardownStalenessSubscription(domainObject);
this.teardownStalenessUtils(domainObject);
delete this.stalenessSubscription[id];
});
if (callback && typeof callback === 'function') {
callback();
}
},
setupStalenessUtils(domainObject) {
const id = this.getSubscriptionId(domainObject);
if (this.stalenessSubscription[id]) {
return;
}
this.stalenessSubscription[id] = {};
this.stalenessSubscription[id].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
},
teardownStalenessUtils(domainObject) {
const id = this.getSubscriptionId(domainObject);
const { stalenessUtils } = this.stalenessSubscription[id];
if (stalenessUtils) {
stalenessUtils.destroy();
delete this.stalenessSubscription[id].stalenessUtils;
}
},
setupStalenessSubscription(domainObject, callback) {
const id = this.getSubscriptionId(domainObject);
this.stalenessSubscription[id].unsubscribe = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStalenessResponse(stalenessResponse, callback);
const SKIP_CHECK = false;
this.handleStalenessResponse(id, stalenessResponse, SKIP_CHECK, callback);
}
);
},
async requestStaleness(domainObject) {
const stalenessResponse = await this.openmct.telemetry.isStale(domainObject);
if (stalenessResponse !== undefined) {
this.handleStalenessResponse(stalenessResponse);
teardownStalenessSubscription(domainObject) {
const id = this.getSubscriptionId(domainObject);
const { unsubscribe } = this.stalenessSubscription[id];
if (unsubscribe) {
unsubscribe();
delete this.stalenessSubscription[id].unsubscribe;
}
},
handleStalenessResponse(stalenessResponse, callback) {
if (this.stalenessUtils.shouldUpdateStaleness(stalenessResponse)) {
if (typeof callback === 'function') {
callback(stalenessResponse.isStale);
resubscribeToStaleness(domainObject, callback, unsubscribeCallback) {
const id = this.getSubscriptionId(domainObject);
this.stalenessSubscription[id].resubscribe = () => {
this.staleObjects = [];
this.triggerUnsubscribeFromStaleness(domainObject, unsubscribeCallback);
this.setupStalenessSubscription(domainObject, callback);
};
},
async requestStaleness(domainObject, callback) {
const id = this.getSubscriptionId(domainObject);
const stalenessResponse = await this.openmct.telemetry.isStale(domainObject);
if (stalenessResponse !== undefined) {
const SKIP_CHECK = false;
this.handleStalenessResponse(id, stalenessResponse, SKIP_CHECK, callback);
}
},
handleStalenessResponse(id, stalenessResponse, skipCheck, callback) {
if (!id) {
id = Object.keys(this.stalenessSubscription)[0];
}
const shouldUpdateStaleness =
this.stalenessSubscription[id].stalenessUtils.shouldUpdateStaleness(stalenessResponse);
if (skipCheck || shouldUpdateStaleness) {
if (callback && typeof callback === 'function') {
callback(stalenessResponse);
} else {
this.isStale = stalenessResponse.isStale;
this.addOrRemoveStaleObject(id, stalenessResponse);
}
}
},
triggerUnsubscribeFromStaleness() {
if (this.unsubscribeFromStaleness) {
this.unsubscribeFromStaleness();
delete this.unsubscribeFromStaleness;
this.stalenessUtils.destroy();
addOrRemoveStaleObject(id, stalenessResponse) {
const index = this.staleObjects.indexOf(id);
if (stalenessResponse.isStale) {
if (index === -1) {
this.staleObjects.push(id);
}
} else {
if (index !== -1) {
this.staleObjects.splice(index, 1);
}
}
}
},
unmounted() {
let compositionObjects = [];
for (const [, object] of this.compositionObjectMap) {
compositionObjects.push(object);
}
this.triggerUnsubscribeFromStaleness(compositionObjects);
if (this.setupClockChanged) {
this.openmct.time.off('clockChanged', this.compositionIteratorCallback);
this.setupClockChanged = false;
}
}
};

View File

@ -33,8 +33,10 @@ export default class StalenessUtils {
shouldUpdateStaleness(stalenessResponse, id) {
const stalenessResponseTime = this.parseTime(stalenessResponse);
const { start } = this.openmct.time.bounds();
const isStalenessInCurrentClock = stalenessResponseTime > start;
if (stalenessResponseTime > this.lastStalenessResponseTime) {
if (stalenessResponseTime > this.lastStalenessResponseTime && isStalenessInCurrentClock) {
this.lastStalenessResponseTime = stalenessResponseTime;
return true;

View File

@ -1,8 +1,17 @@
export function convertTemplateToHTML(templateString) {
const template = document.createElement('template');
template.innerHTML = templateString;
const parser = new DOMParser();
const doc = parser.parseFromString(templateString, 'text/html');
return template.content.cloneNode(true).children;
// Create a document fragment to hold the parsed content
const fragment = document.createDocumentFragment();
// Append nodes from the parsed content to the fragment
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}
// Convert children of the fragment to an array and return
return Array.from(fragment.children);
}
export function toggleClass(element, className) {