mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 12:56:25 +00:00
Prevent Metadata Time System Error for Missing Objects (#7565)
https://github.com/nasa/openmct/pull/7565 Modified Stacked Plots to not show Missing Objects. Added a check in Telemetry Collections for missing objects before displaying telemetry metadata time system error.
This commit is contained in:
parent
faed27c143
commit
10eb749d32
@ -24,138 +24,60 @@
|
||||
Tests to verify log plot functionality when objects are missing
|
||||
*/
|
||||
|
||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Handle missing object for plots', () => {
|
||||
test('Displays empty div for missing stacked plot item @unstable', async ({
|
||||
page,
|
||||
browserName,
|
||||
openmctConfig
|
||||
}) => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test('Displays empty div for missing stacked plot item', async ({ page, browserName }) => {
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed');
|
||||
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
const errorLogs = [];
|
||||
let warningReceived = false;
|
||||
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'warning' && message.text().includes('Missing domain object')) {
|
||||
errorLogs.push(message.text());
|
||||
warningReceived = true;
|
||||
}
|
||||
});
|
||||
|
||||
//Make stacked plot
|
||||
await makeStackedPlot(page, myItemsFolderName);
|
||||
const stackedPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Stacked Plot'
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: stackedPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: stackedPlot.uuid
|
||||
});
|
||||
|
||||
//Gets local storage and deletes the last sine wave generator in the stacked plot
|
||||
const localStorage = await page.evaluate(() => window.localStorage);
|
||||
const parsedData = JSON.parse(localStorage.mct);
|
||||
const keys = Object.keys(parsedData);
|
||||
const lastKey = keys[keys.length - 1];
|
||||
const mct = await page.evaluate(() => window.localStorage.getItem('mct'));
|
||||
const parsedData = JSON.parse(mct);
|
||||
const key = Object.entries(parsedData).find(([, value]) => value.type === 'generator')?.[0];
|
||||
|
||||
delete parsedData[lastKey];
|
||||
delete parsedData[key];
|
||||
|
||||
//Sets local storage with missing object
|
||||
await page.evaluate(`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`);
|
||||
const jsonData = JSON.stringify(parsedData);
|
||||
await page.evaluate((data) => {
|
||||
window.localStorage.setItem('mct', data);
|
||||
}, jsonData);
|
||||
|
||||
//Reloads page and clicks on stacked plot
|
||||
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.goto(stackedPlot.url);
|
||||
|
||||
//Verify Main section is there on load
|
||||
await expect
|
||||
.soft(page.locator('.l-browse-bar__object-name'))
|
||||
.toContainText('Unnamed Stacked Plot');
|
||||
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText(stackedPlot.name);
|
||||
|
||||
//Check that there is only one stacked item plot with a plot, the missing one will be empty
|
||||
await expect(page.locator('.c-plot--stacked-container:has(.gl-plot)')).toHaveCount(1);
|
||||
//Verify that console.warn is thrown
|
||||
expect(errorLogs).toHaveLength(1);
|
||||
await expect(page.getByLabel('Stacked Plot Item')).toHaveCount(1);
|
||||
//Verify that console.warn was thrown
|
||||
expect(warningReceived).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This is used the create a stacked plot object
|
||||
* @private
|
||||
*/
|
||||
async function makeStackedPlot(page, myItemsFolderName) {
|
||||
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// create stacked plot
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
||||
page.locator('button:has-text("OK")').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
|
||||
// save the stacked plot
|
||||
await saveStackedPlot(page);
|
||||
|
||||
// create a sinewave generator
|
||||
await createSineWaveGenerator(page);
|
||||
|
||||
// click on stacked plot
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
|
||||
// create a second sinewave generator
|
||||
await createSineWaveGenerator(page);
|
||||
|
||||
// click on stacked plot
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to save a stacked plot object
|
||||
* @private
|
||||
*/
|
||||
async function saveStackedPlot(page) {
|
||||
// save stacked plot
|
||||
await page
|
||||
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
|
||||
.nth(1)
|
||||
.click();
|
||||
|
||||
await Promise.all([
|
||||
page.locator('text=Save and Finish Editing').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to create a sine wave generator object
|
||||
* @private
|
||||
*/
|
||||
async function createSineWaveGenerator(page) {
|
||||
//Create sine wave generator
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
||||
page.locator('button:has-text("OK")').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ export default class ObjectAPI {
|
||||
.get(identifier, abortSignal)
|
||||
.then((domainObject) => {
|
||||
delete this.cache[keystring];
|
||||
if (!domainObject && abortSignal.aborted) {
|
||||
if (!domainObject && abortSignal?.aborted) {
|
||||
// we've aborted the request
|
||||
return;
|
||||
}
|
||||
|
@ -442,8 +442,12 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
} else {
|
||||
this.timeKey = undefined;
|
||||
|
||||
this._warn(TIMESYSTEM_KEY_WARNING);
|
||||
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
|
||||
// missing objects will never have a domain, if one happens to get through
|
||||
// to this point this warning/notification does not apply
|
||||
if (!this.openmct.objects.isMissing(this.domainObject)) {
|
||||
this._warn(TIMESYSTEM_KEY_WARNING);
|
||||
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
|
||||
}
|
||||
}
|
||||
|
||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
|
@ -46,6 +46,11 @@ export default class SeriesCollection extends Collection {
|
||||
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);
|
||||
|
||||
const domainObject = this.plot.get('domainObject');
|
||||
|
||||
if (this.openmct.objects.isMissing(domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (domainObject.telemetry) {
|
||||
this.addTelemetryObject(domainObject);
|
||||
} else {
|
||||
|
@ -219,6 +219,11 @@ export default {
|
||||
},
|
||||
|
||||
addChild(child) {
|
||||
if (this.openmct.objects.isMissing(child)) {
|
||||
console.warn('Missing domain object for stacked plot: ', child);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(child.identifier);
|
||||
|
||||
this.tickWidthMap[id] = {
|
||||
|
@ -170,6 +170,10 @@ export default {
|
||||
//If this object is not persistable, then package it with it's parent
|
||||
const plotObject = this.getPlotObject();
|
||||
|
||||
if (plotObject === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.telemetry.isTelemetryObject(plotObject)) {
|
||||
this.subscribeToStaleness(plotObject);
|
||||
} else {
|
||||
@ -215,10 +219,6 @@ export default {
|
||||
},
|
||||
getPlotObject() {
|
||||
this.checkPlotConfiguration();
|
||||
// If object is missing, warn
|
||||
if (this.openmct.objects.isMissing(this.childObject)) {
|
||||
console.warn('Missing domain object for stacked plot', this.childObject);
|
||||
}
|
||||
return this.childObject;
|
||||
},
|
||||
checkPlotConfiguration() {
|
||||
|
@ -190,7 +190,7 @@ export default {
|
||||
this.soViewResizeObserver.observe(this.$refs.soView);
|
||||
}
|
||||
|
||||
const viewKey = this.getViewKey();
|
||||
const viewKey = this.$refs.objectView?.viewKey;
|
||||
this.supportsIndependentTime = this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
},
|
||||
beforeUnmount() {
|
||||
@ -257,9 +257,6 @@ export default {
|
||||
|
||||
this.widthClass = wClass.trimStart();
|
||||
},
|
||||
getViewKey() {
|
||||
return this.$refs.objectView?.viewKey;
|
||||
},
|
||||
async showToolTip() {
|
||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||
this.buildToolTip(await this.getObjectPath(), BELOW, 'objectName');
|
||||
|
@ -29,7 +29,7 @@
|
||||
@click="goToParent"
|
||||
></button>
|
||||
<div class="l-browse-bar__object-name--w c-object-label" :class="[statusClass]">
|
||||
<div class="c-object-label__type-icon" :class="type.cssClass">
|
||||
<div class="c-object-label__type-icon" :class="cssClass">
|
||||
<span class="is-status__indicator" :title="`This item is ${status}`"></span>
|
||||
</div>
|
||||
<span
|
||||
@ -43,7 +43,7 @@
|
||||
@mouseover.ctrl="showToolTip"
|
||||
@mouseleave="hideToolTip"
|
||||
>
|
||||
{{ domainObject.name }}
|
||||
{{ domainObjectName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,8 +150,6 @@ import tooltipHelpers from '../../api/tooltips/tooltipMixins.js';
|
||||
import { SupportedViewTypes } from '../../utils/constants.js';
|
||||
import ViewSwitcher from './ViewSwitcher.vue';
|
||||
|
||||
const PLACEHOLDER_OBJECT = {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IndependentTimeConductor,
|
||||
@ -168,12 +166,12 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
notebookTypes: [],
|
||||
showViewMenu: false,
|
||||
showSaveMenu: false,
|
||||
domainObject: PLACEHOLDER_OBJECT,
|
||||
domainObject: undefined,
|
||||
viewKey: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
notebookEnabled: this.openmct.types.get('notebook'),
|
||||
@ -185,11 +183,22 @@ export default {
|
||||
statusClass() {
|
||||
return this.status ? `is-status--${this.status}` : '';
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
return (
|
||||
this.domainObject?.identifier &&
|
||||
!this.openmct.objects.isMissing(this.domainObject) &&
|
||||
SupportedViewTypes.includes(this.viewKey)
|
||||
);
|
||||
},
|
||||
currentView() {
|
||||
return this.views.filter((v) => v.key === this.viewKey)[0] || {};
|
||||
},
|
||||
views() {
|
||||
if (this.domainObject && this.openmct.router.started !== true) {
|
||||
if (this.domainObject && this.openmct.router.started === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.domainObject) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -203,25 +212,29 @@ export default {
|
||||
});
|
||||
},
|
||||
hasParent() {
|
||||
return toRaw(this.domainObject) !== PLACEHOLDER_OBJECT && this.parentUrl !== '/browse';
|
||||
return toRaw(this.domainObject) && this.parentUrl !== '/browse';
|
||||
},
|
||||
parentUrl() {
|
||||
const objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const objectKeyString = this.openmct.objects.makeKeyString(this.domainObject?.identifier);
|
||||
const hash = this.openmct.router.getCurrentLocation().path;
|
||||
|
||||
return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
|
||||
},
|
||||
type() {
|
||||
const objectType = this.openmct.types.get(this.domainObject.type);
|
||||
if (!objectType) {
|
||||
return {};
|
||||
cssClass() {
|
||||
if (!this.domainObject) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return objectType.definition;
|
||||
const objectType = this.openmct.types.get(this.domainObject.type);
|
||||
if (!objectType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return objectType?.definition?.cssClass ?? '';
|
||||
},
|
||||
isPersistable() {
|
||||
let persistable =
|
||||
this.domainObject.identifier &&
|
||||
const persistable =
|
||||
this.domainObject?.identifier &&
|
||||
this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
|
||||
return persistable;
|
||||
@ -246,10 +259,8 @@ export default {
|
||||
return 'Unlocked for editing - click to lock.';
|
||||
}
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
const viewKey = this.getViewKey();
|
||||
|
||||
return this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
domainObjectName() {
|
||||
return this.domainObject?.name ?? '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -273,7 +284,7 @@ export default {
|
||||
this.updateActionItems(this.actionCollection.getActionsObject());
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
mounted() {
|
||||
document.addEventListener('click', this.closeViewAndSaveMenu);
|
||||
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
|
||||
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
||||
@ -282,7 +293,7 @@ export default {
|
||||
this.isEditing = isEditing;
|
||||
});
|
||||
},
|
||||
beforeUnmount: function () {
|
||||
beforeUnmount() {
|
||||
if (this.mutationObserver) {
|
||||
this.mutationObserver();
|
||||
}
|
||||
@ -323,9 +334,6 @@ export default {
|
||||
edit() {
|
||||
this.openmct.editor.edit();
|
||||
},
|
||||
getViewKey() {
|
||||
return this.viewKey;
|
||||
},
|
||||
promptUserandCancelEditing() {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
|
Loading…
Reference in New Issue
Block a user