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:compat/recommended',
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'plugin:you-dont-need-lodash-underscore/compatible', 'plugin:you-dont-need-lodash-underscore/compatible',
'plugin:prettier/recommended' 'plugin:prettier/recommended',
'plugin:no-unsanitized/DOM'
], ],
parser: 'vue-eslint-parser', parser: 'vue-eslint-parser',
parserOptions: { 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 ## 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 To start a minimally functional Open MCT application, it is necessary to
include the Open MCT distributable, enable some basic plugins, and bootstrap include the Open MCT distributable, enable some basic plugins, and bootstrap
the application. The tutorials walk through the process of getting Open MCT up the application. The tutorials walk through the process of getting Open MCT up

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -161,8 +161,11 @@ export default {
let originalClassName = this.dragGhost.classList[0]; let originalClassName = this.dragGhost.classList[0];
this.dragGhost.className = ''; this.dragGhost.className = '';
this.dragGhost.classList.add(originalClassName, iconClass); 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); 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('bounds', this.refreshData);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
}, },
unmounted() { unmounted() {
this.composition.off('add', this.addedToComposition); this.composition.off('add', this.addedToComposition);
@ -620,7 +625,7 @@ export default {
this.unsubscribe = null; this.unsubscribe = null;
} }
this.triggerUnsubscribeFromStaleness(); this.triggerUnsubscribeFromStaleness(this.domainObject);
this.curVal = DEFAULT_CURRENT_VALUE; this.curVal = DEFAULT_CURRENT_VALUE;
this.formats = null; this.formats = null;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ import mount from 'utils/mount';
import configStore from '@/plugins/plot/configuration/ConfigStore'; import configStore from '@/plugins/plot/configuration/ConfigStore';
import PlotConfigurationModel from '@/plugins/plot/configuration/PlotConfigurationModel'; import PlotConfigurationModel from '@/plugins/plot/configuration/PlotConfigurationModel';
import stalenessMixin from '@/ui/mixins/staleness-mixin'; import stalenessMixin from '@/ui/mixins/staleness-mixin';
import StalenessUtils from '@/utils/staleness';
import Plot from '../Plot.vue'; import Plot from '../Plot.vue';
import conditionalStylesMixin from './mixins/objectStyles-mixin'; import conditionalStylesMixin from './mixins/objectStyles-mixin';
@ -90,11 +89,6 @@ export default {
} }
} }
}, },
data() {
return {
staleObjects: []
};
},
watch: { watch: {
gridLines(newGridLines) { gridLines(newGridLines) {
this.updateComponentProp('gridLines', newGridLines); this.updateComponentProp('gridLines', newGridLines);
@ -116,20 +110,26 @@ export default {
}, },
staleObjects: { staleObjects: {
handler() { handler() {
this.isStale = this.staleObjects.length > 0;
this.updateComponentProp('isStale', this.isStale); this.updateComponentProp('isStale', this.isStale);
}, },
deep: true deep: true
} }
}, },
mounted() { mounted() {
this.stalenessSubscription = {};
this.updateView(); this.updateView();
this.isEditing = this.openmct.editor.isEditing(); this.isEditing = this.openmct.editor.isEditing();
this.openmct.editor.on('isEditing', this.setEditState); this.openmct.editor.on('isEditing', this.setEditState);
this.setupClockChangedEvent((domainObject) => {
this.triggerUnsubscribeFromStaleness(domainObject);
this.subscribeToStaleness(domainObject);
});
}, },
beforeUnmount() { beforeUnmount() {
this.openmct.editor.off('isEditing', this.setEditState); 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) { if (this.removeSelectable) {
this.removeSelectable(); this.removeSelectable();
@ -141,8 +141,6 @@ export default {
if (this._destroy) { if (this._destroy) {
this._destroy(); this._destroy();
} }
this.destroyStalenessListeners();
}, },
methods: { methods: {
setEditState(isEditing) { setEditState(isEditing) {
@ -163,10 +161,6 @@ export default {
} }
}, },
updateView() { updateView() {
this.isStale = false;
this.destroyStalenessListeners();
if (this._destroy) { if (this._destroy) {
this._destroy(); this._destroy();
this.component = null; this.component = null;
@ -190,15 +184,15 @@ export default {
const isMissing = openmct.objects.isMissing(object); const isMissing = openmct.objects.isMissing(object);
if (this.openmct.telemetry.isTelemetryObject(object)) { if (this.openmct.telemetry.isTelemetryObject(object)) {
this.subscribeToStaleness(object, (isStale) => { this.subscribeToStaleness(object, (stalenessResponse) => {
this.updateComponentProp('isStale', isStale); this.updateComponentProp('isStale', stalenessResponse.isStale);
}); });
} else { } else {
// possibly overlay or other composition based plot // possibly overlay or other composition based plot
this.composition = this.openmct.composition.get(object); this.composition = this.openmct.composition.get(object);
this.composition.on('add', this.watchStaleness); this.composition.on('add', this.subscribeToStaleness);
this.composition.on('remove', this.unwatchStaleness); this.composition.on('remove', this.triggerUnsubscribeFromStaleness);
this.composition.load(); this.composition.load();
} }
@ -260,54 +254,6 @@ export default {
this.setSelection(); 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() { onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments); this.$emit('lockHighlightPoint', ...arguments);
}, },
@ -405,20 +351,6 @@ export default {
return this.childObject; 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(); const options = this.generateSelectOptions();
newInput = document.createElement('select'); newInput = document.createElement('select');
newInput.innerHTML = options; newInput.appendChild(options);
emitChange = true; emitChange = true;
} else { } else {
@ -244,12 +244,16 @@ define([
Condition.prototype.generateSelectOptions = function () { Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object); let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let options = ''; let fragment = document.createDocumentFragment();
telemetryMetadata[this.config.key].enumerations.forEach((enumeration) => { 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; return Condition;

View File

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

View File

@ -247,9 +247,10 @@ define([
SummaryWidget.prototype.updateWidget = function () { SummaryWidget.prototype.updateWidget = function () {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon'; const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
const activeRule = this.rulesById[this.activeId]; const activeRule = this.rulesById[this.activeId];
this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style')); this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style'));
this.domElement.querySelector('#widget').title = activeRule.getProperty('message'); 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 = this.domElement.querySelector('#widgetIcon').classList =
WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon'); WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon');
}; };

View File

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

View File

@ -1,35 +1,60 @@
define(['./summary-widget.html', '@braintree/sanitize-url'], function ( import * as urlSanitizeLib from '@braintree/sanitize-url';
summaryWidgetTemplate,
urlSanitizeLib
) {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
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.openmct = openmct;
this.domainObject = domainObject; this.domainObject = domainObject;
this.hasUpdated = false; this.hasUpdated = false;
this.render = this.render.bind(this); this.render = this.render.bind(this);
} }
SummaryWidgetView.prototype.updateState = function (datum) { updateState(datum) {
this.hasUpdated = true; this.hasUpdated = true;
this.widget.style.color = datum.textColor; this.widget.style.color = datum.textColor;
this.widget.style.backgroundColor = datum.backgroundColor; this.widget.style.backgroundColor = datum.backgroundColor;
this.widget.style.borderColor = datum.borderColor; this.widget.style.borderColor = datum.borderColor;
this.widget.title = datum.message; this.widget.title = datum.message;
this.label.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; this.icon.className = WIDGET_ICON_CLASS + ' ' + datum.icon;
}; }
SummaryWidgetView.prototype.render = function () { render() {
if (this.unsubscribe) { if (this.unsubscribe) {
this.unsubscribe(); this.unsubscribe();
} }
this.hasUpdated = false; this.hasUpdated = false;
this.container.innerHTML = summaryWidgetTemplate; const anchor = this.#createSummaryWidgetTemplate();
this.container.appendChild(anchor);
this.widget = this.container.querySelector('a'); this.widget = this.container.querySelector('a');
this.icon = this.container.querySelector('#widgetIcon'); this.icon = this.container.querySelector('#widgetIcon');
this.label = this.container.querySelector('.js-sw__label'); this.label = this.container.querySelector('.js-sw__label');
@ -49,13 +74,13 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
const renderTracker = {}; const renderTracker = {};
this.renderTracker = renderTracker; this.renderTracker = renderTracker;
this.openmct.telemetry this.openmct.telemetry
.request(this.domainObject, { .request(this.domainObject, {
strategy: 'latest', strategy: 'latest',
size: 1 size: 1
}) })
.then( .then((results) => {
function (results) {
if ( if (
this.destroyed || this.destroyed ||
this.hasUpdated || this.hasUpdated ||
@ -66,16 +91,15 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
} }
this.updateState(results[results.length - 1]); this.updateState(results[results.length - 1]);
}.bind(this) });
);
this.unsubscribe = this.openmct.telemetry.subscribe( this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject, this.domainObject,
this.updateState.bind(this) this.updateState.bind(this)
); );
}; }
SummaryWidgetView.prototype.show = function (container) { show(container) {
this.container = container; this.container = container;
this.render(); this.render();
this.removeMutationListener = this.openmct.objects.observe( this.removeMutationListener = this.openmct.objects.observe(
@ -84,14 +108,14 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
this.onMutation.bind(this) this.onMutation.bind(this)
); );
this.openmct.time.on('timeSystem', this.render); this.openmct.time.on('timeSystem', this.render);
}; }
SummaryWidgetView.prototype.onMutation = function (domainObject) { onMutation(domainObject) {
this.domainObject = domainObject; this.domainObject = domainObject;
this.render(); this.render();
}; }
SummaryWidgetView.prototype.destroy = function (container) { destroy() {
this.unsubscribe(); this.unsubscribe();
this.removeMutationListener(); this.removeMutationListener();
this.openmct.time.off('timeSystem', this.render); this.openmct.time.off('timeSystem', this.render);
@ -100,7 +124,7 @@ define(['./summary-widget.html', '@braintree/sanitize-url'], function (
delete this.label; delete this.label;
delete this.openmct; delete this.openmct;
delete this.domainObject; 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.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.telemetryObjects = {}; this.telemetryObjects = {};
this.subscribedStaleObjects = new Map();
this.telemetryCollections = {}; this.telemetryCollections = {};
this.delayedActions = []; this.delayedActions = [];
this.outstandingRequests = 0; this.outstandingRequests = 0;
@ -74,6 +75,8 @@ define([
this.filterObserver = undefined; this.filterObserver = undefined;
this.createTableRowCollections(); 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].on('clear', this.clearData);
this.telemetryCollections[keyString].load(); this.telemetryCollections[keyString].load();
this.stalenessSubscription[keyString] = {}; this.subscribeToStaleness(telemetryObject);
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.telemetryObjects[keyString] = { this.telemetryObjects[keyString] = {
telemetryObject, telemetryObject,
@ -193,6 +179,42 @@ define([
this.emit('object-added', telemetryObject); 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) { handleStaleness(keyString, stalenessResponse, skipCheck = false) {
if ( if (
skipCheck || skipCheck ||
@ -203,7 +225,7 @@ define([
) { ) {
this.emit('telemetry-staleness', { this.emit('telemetry-staleness', {
keyString, keyString,
isStale: stalenessResponse.isStale stalenessResponse: stalenessResponse
}); });
} }
} }
@ -310,7 +332,6 @@ define([
removeTelemetryObject(objectIdentifier) { removeTelemetryObject(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier); const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
const SKIP_CHECK = true;
this.configuration.removeColumnsForObject(objectIdentifier, true); this.configuration.removeColumnsForObject(objectIdentifier, true);
this.tableRows.removeRowsByObject(keyString); this.tableRows.removeRowsByObject(keyString);
@ -320,6 +341,13 @@ define([
this.emit('object-removed', objectIdentifier); 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].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy(); this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK); this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
@ -423,6 +451,7 @@ define([
this.tableRows.destroy(); this.tableRows.destroy();
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData); this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
this.openmct.time.off('clockChanged', this.resubscribeToStaleness);
let keystrings = Object.keys(this.telemetryCollections); let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection); keystrings.forEach(this.removeTelemetryCollection);

View File

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

View File

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

View File

@ -20,52 +20,190 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import { isProxy, toRaw } from 'vue';
import StalenessUtils from '@/utils/staleness'; import StalenessUtils from '@/utils/staleness';
export default { export default {
data() { data() {
return { return {
isStale: false staleObjects: [],
stalenessSubscription: {},
compositionObjectMap: new Map(),
setupClockChanged: false
}; };
}, },
beforeUnmount() { computed: {
this.triggerUnsubscribeFromStaleness(); isStale() {
return this.staleObjects.length !== 0;
}
}, },
methods: { methods: {
subscribeToStaleness(domainObject, callback) { getSubscriptionId(domainObject) {
if (!this.stalenessUtils) { return this.openmct?.objects.makeKeyString(domainObject.identifier);
this.stalenessUtils = new StalenessUtils(this.openmct, domainObject); },
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); domainObjectList.forEach((domainObject) => {
this.unsubscribeFromStaleness = this.openmct.telemetry.subscribeToStaleness( 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, domainObject,
(stalenessResponse) => { (stalenessResponse) => {
this.handleStalenessResponse(stalenessResponse, callback); const SKIP_CHECK = false;
this.handleStalenessResponse(id, stalenessResponse, SKIP_CHECK, callback);
} }
); );
}, },
async requestStaleness(domainObject) { teardownStalenessSubscription(domainObject) {
const id = this.getSubscriptionId(domainObject);
const { unsubscribe } = this.stalenessSubscription[id];
if (unsubscribe) {
unsubscribe();
delete this.stalenessSubscription[id].unsubscribe;
}
},
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); const stalenessResponse = await this.openmct.telemetry.isStale(domainObject);
if (stalenessResponse !== undefined) { if (stalenessResponse !== undefined) {
this.handleStalenessResponse(stalenessResponse); const SKIP_CHECK = false;
this.handleStalenessResponse(id, stalenessResponse, SKIP_CHECK, callback);
} }
}, },
handleStalenessResponse(stalenessResponse, callback) { handleStalenessResponse(id, stalenessResponse, skipCheck, callback) {
if (this.stalenessUtils.shouldUpdateStaleness(stalenessResponse)) { if (!id) {
if (typeof callback === 'function') { id = Object.keys(this.stalenessSubscription)[0];
callback(stalenessResponse.isStale); }
const shouldUpdateStaleness =
this.stalenessSubscription[id].stalenessUtils.shouldUpdateStaleness(stalenessResponse);
if (skipCheck || shouldUpdateStaleness) {
if (callback && typeof callback === 'function') {
callback(stalenessResponse);
} else { } else {
this.isStale = stalenessResponse.isStale; this.addOrRemoveStaleObject(id, stalenessResponse);
} }
} }
}, },
triggerUnsubscribeFromStaleness() { addOrRemoveStaleObject(id, stalenessResponse) {
if (this.unsubscribeFromStaleness) { const index = this.staleObjects.indexOf(id);
this.unsubscribeFromStaleness(); if (stalenessResponse.isStale) {
delete this.unsubscribeFromStaleness; if (index === -1) {
this.stalenessUtils.destroy(); 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) { shouldUpdateStaleness(stalenessResponse, id) {
const stalenessResponseTime = this.parseTime(stalenessResponse); 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; this.lastStalenessResponseTime = stalenessResponseTime;
return true; return true;

View File

@ -1,8 +1,17 @@
export function convertTemplateToHTML(templateString) { export function convertTemplateToHTML(templateString) {
const template = document.createElement('template'); const parser = new DOMParser();
template.innerHTML = templateString; 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) { export function toggleClass(element, className) {