mirror of
https://github.com/nasa/openmct.git
synced 2025-03-21 03:25:44 +00:00
Handle missing objects gracefully (#5399)
* Handle missing object errors for display layouts * Handle missing object errors for Overlay Plots * Add check for this.config * Add try/catch statement & check if obj is missing * Changed console.error to console.warn * Lint fix * Fix for this.metadata.value is undefined * Add e2e test * Update comment text * Add reload check and @private, verify console.warn * Redid assignment and metadata check * Fix typo * Changed assignment and metadata check * Redid checks for isMissing(object) * Lint fix
This commit is contained in:
parent
2999a5135e
commit
a07c043a29
161
e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js
Normal file
161
e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js
Normal file
@ -0,0 +1,161 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Tests to verify log plot functionality when objects are missing
|
||||
*/
|
||||
|
||||
const { test } = require('../../../fixtures.js');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Handle missing object for plots', () => {
|
||||
test('Displays empty div for missing stacked plot item', async ({ page }) => {
|
||||
const errorLogs = [];
|
||||
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === 'warning') {
|
||||
errorLogs.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
//Make stacked plot
|
||||
await makeStackedPlot(page);
|
||||
|
||||
//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];
|
||||
|
||||
delete parsedData[lastKey];
|
||||
|
||||
//Sets local storage with missing object
|
||||
await page.evaluate(
|
||||
`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`
|
||||
);
|
||||
|
||||
//Reloads page and clicks on stacked plot
|
||||
await Promise.all([
|
||||
page.reload(),
|
||||
page.waitForLoadState('networkidle')
|
||||
]);
|
||||
|
||||
//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 My Items >> span').nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
|
||||
//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
|
||||
await expect(errorLogs).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This is used the create a stacked plot object
|
||||
* @private
|
||||
*/
|
||||
async function makeStackedPlot(page) {
|
||||
// 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: 'networkidle' });
|
||||
|
||||
// create stacked plot
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li:has-text("Stacked Plot")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||
page.locator('text=OK').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'});
|
||||
|
||||
// save the stacked plot
|
||||
await saveStackedPlot(page);
|
||||
|
||||
// create a sinewave generator
|
||||
await createSineWaveGenerator(page);
|
||||
|
||||
// click on stacked plot
|
||||
await page.locator('text=Open MCT My Items >> 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 My Items >> 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:has-text("Sine Wave Generator")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||
page.locator('text=OK').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'});
|
||||
}
|
@ -529,7 +529,7 @@ define([
|
||||
}
|
||||
|
||||
this.openmct.notifications.error(message);
|
||||
console.error(detailMessage);
|
||||
console.warn(detailMessage);
|
||||
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
@ -322,7 +322,14 @@ export class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_setTimeSystem(timeSystem) {
|
||||
let domains = this.metadata.valuesForHints(['domain']);
|
||||
let domains = [];
|
||||
let metadataValue = { format: timeSystem.key };
|
||||
|
||||
if (this.metadata) {
|
||||
domains = this.metadata.valuesForHints(['domain']);
|
||||
metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
|
||||
}
|
||||
|
||||
let domain = domains.find((d) => d.key === timeSystem.key);
|
||||
|
||||
if (domain !== undefined) {
|
||||
@ -335,7 +342,6 @@ export class TelemetryCollection extends EventEmitter {
|
||||
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
|
||||
}
|
||||
|
||||
let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
|
||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
|
||||
this.parseTime = (datum) => {
|
||||
|
@ -152,7 +152,7 @@ export default {
|
||||
},
|
||||
unit() {
|
||||
let value = this.item.value;
|
||||
let unit = this.metadata.value(value).unit;
|
||||
let unit = this.metadata ? this.metadata.value(value).unit : '';
|
||||
|
||||
return unit;
|
||||
},
|
||||
@ -280,7 +280,7 @@ export default {
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
|
||||
const valueMetadata = this.metadata.value(this.item.value);
|
||||
const valueMetadata = this.metadata ? this.metadata.value(this.item.value) : {};
|
||||
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
||||
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||
|
@ -135,17 +135,21 @@ export default {
|
||||
},
|
||||
setUpXAxisOptions() {
|
||||
const xAxisKey = this.xAxis.get('key');
|
||||
this.xKeyOptions = [];
|
||||
|
||||
if (this.seriesModel.metadata) {
|
||||
this.xKeyOptions = this.seriesModel.metadata
|
||||
.valuesForHints(['domain'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.name,
|
||||
key: o.key
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
this.xKeyOptions = this.seriesModel.metadata
|
||||
.valuesForHints(['domain'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.name,
|
||||
key: o.key
|
||||
};
|
||||
});
|
||||
this.xAxisLabel = this.xAxis.get('label');
|
||||
this.selectedXKeyOptionKey = this.getXKeyOption(xAxisKey).key;
|
||||
this.selectedXKeyOptionKey = this.xKeyOptions.length > 0 ? this.getXKeyOption(xAxisKey).key : xAxisKey;
|
||||
},
|
||||
onTickWidthChange(width) {
|
||||
this.$emit('tickWidthChanged', width);
|
||||
|
@ -120,21 +120,25 @@ export default {
|
||||
}
|
||||
},
|
||||
setUpYAxisOptions() {
|
||||
this.yKeyOptions = this.seriesModel.metadata
|
||||
.valuesForHints(['range'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.name,
|
||||
key: o.key
|
||||
};
|
||||
});
|
||||
this.yKeyOptions = [];
|
||||
|
||||
if (this.seriesModel.metadata) {
|
||||
this.yKeyOptions = this.seriesModel.metadata
|
||||
.valuesForHints(['range'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.name,
|
||||
key: o.key
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// set yAxisLabel if none is set yet
|
||||
if (this.yAxisLabel === 'none') {
|
||||
let yKey = this.seriesModel.model.yKey;
|
||||
let yKeyModel = this.yKeyOptions.filter(o => o.key === yKey)[0];
|
||||
|
||||
this.yAxisLabel = yKeyModel.name;
|
||||
this.yAxisLabel = yKeyModel ? yKeyModel.name : '';
|
||||
}
|
||||
},
|
||||
toggleYAxisLabel() {
|
||||
|
@ -197,25 +197,27 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
initConfiguration() {
|
||||
this.label = this.config.yAxis.get('label');
|
||||
this.autoscale = this.config.yAxis.get('autoscale');
|
||||
this.logMode = this.config.yAxis.get('logMode');
|
||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||
const range = this.config.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
if (this.config) {
|
||||
this.label = this.config.yAxis.get('label');
|
||||
this.autoscale = this.config.yAxis.get('autoscale');
|
||||
this.logMode = this.config.yAxis.get('logMode');
|
||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||
const range = this.config.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
|
||||
this.position = this.config.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.config.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.config.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.config.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.config.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.config.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.config.legend.get('showUnitsWhenExpanded');
|
||||
this.position = this.config.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.config.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.config.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.config.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.config.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.config.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.config.legend.get('showUnitsWhenExpanded');
|
||||
}
|
||||
},
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@ -223,10 +225,12 @@ export default {
|
||||
return configStore.get(this.configId);
|
||||
},
|
||||
registerListeners() {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
if (this.config) {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
}
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
|
@ -134,6 +134,7 @@ export default {
|
||||
//If this object is not persistable, then package it with it's parent
|
||||
const object = this.getPlotObject();
|
||||
const getProps = this.getProps;
|
||||
const isMissing = openmct.objects.isMissing(object);
|
||||
let viewContainer = document.createElement('div');
|
||||
this.$el.append(viewContainer);
|
||||
|
||||
@ -158,6 +159,7 @@ export default {
|
||||
onCursorGuideChange,
|
||||
onGridLinesChange,
|
||||
setStatus,
|
||||
isMissing,
|
||||
loading: true
|
||||
};
|
||||
},
|
||||
@ -166,7 +168,7 @@ export default {
|
||||
this.loading = loaded;
|
||||
}
|
||||
},
|
||||
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><progress-bar v-show="loading !== false" class="c-telemetry-table__progress-bar" :model="{progressPerc: undefined}" /><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
||||
template: '<div v-if="!isMissing" ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><progress-bar v-show="loading !== false" class="c-telemetry-table__progress-bar" :model="{progressPerc: undefined}" /><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
||||
});
|
||||
|
||||
this.setSelection();
|
||||
@ -220,6 +222,13 @@ export default {
|
||||
//If the object has a configuration, allow initialization of the config from it's persisted config
|
||||
return this.childObject;
|
||||
} else {
|
||||
//If object is missing, warn and return object
|
||||
if (this.openmct.objects.isMissing(this.childObject)) {
|
||||
console.warn('Missing domain object');
|
||||
|
||||
return this.childObject;
|
||||
}
|
||||
|
||||
// If the object does not have configuration, initialize the series config with the persisted config from the stacked plot
|
||||
const configId = this.openmct.objects.makeKeyString(this.childObject.identifier);
|
||||
let config = configStore.get(configId);
|
||||
|
Loading…
x
Reference in New Issue
Block a user