diff --git a/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js index ace4253d12b..47118d6765a 100644 --- a/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js +++ b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js @@ -25,7 +25,10 @@ */ const { test, expect } = require('../../../../pluginFixtures'); -const { createDomainObjectWithDefaults } = require('../../../../appActions'); +const { + createDomainObjectWithDefaults, + createExampleTelemetryObject +} = require('../../../../appActions'); const uuid = require('uuid').v4; test.describe('Gauge', () => { @@ -133,4 +136,50 @@ test.describe('Gauge', () => { // TODO: Verify changes in the UI }); + + test('Gauge does not display NaN when data not available', async ({ page }) => { + // Create a Gauge + const gauge = await createDomainObjectWithDefaults(page, { + type: 'Gauge' + }); + + // Create a Sine Wave Generator in the Gauge with a loading delay + const swgWith5sDelay = await createExampleTelemetryObject(page, gauge.uuid); + + await page.goto(swgWith5sDelay.url); + await page.getByTitle('More options').click(); + await page.getByRole('menuitem', { name: /Edit Properties.../ }).click(); + + //Edit Example Telemetry Object to include 5s loading Delay + await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); + + await page.getByRole('button', { name: 'Save' }).click(); + + // Wait until the URL is updated + await page.waitForURL(`**/${gauge.uuid}/*`); + + // Nav to the Gauge + await page.goto(gauge.url); + const gaugeNoDataText = await page.locator('.js-dial-current-value tspan').textContent(); + expect(gaugeNoDataText).toBe('--'); + }); + + test('Gauge enforces composition policy', async ({ page }) => { + // Create a Gauge + await createDomainObjectWithDefaults(page, { + type: 'Gauge', + name: 'Unnamed Gauge' + }); + + // Try to create a Folder into the Gauge. Should be disallowed. + await page.getByRole('button', { name: /Create/ }).click(); + await page.getByRole('menuitem', { name: /Folder/ }).click(); + await expect(page.locator('[aria-label="Save"]')).toBeDisabled(); + await page.getByLabel('Cancel').click(); + + // Try to create a Display Layout into the Gauge. Should be disallowed. + await page.getByRole('button', { name: /Create/ }).click(); + await page.getByRole('menuitem', { name: /Display Layout/ }).click(); + await expect(page.locator('[aria-label="Save"]')).toBeDisabled(); + }); }); diff --git a/src/plugins/gauge/GaugeCompositionPolicy.js b/src/plugins/gauge/GaugeCompositionPolicy.js new file mode 100644 index 00000000000..7a80e61fac8 --- /dev/null +++ b/src/plugins/gauge/GaugeCompositionPolicy.js @@ -0,0 +1,51 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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. + *****************************************************************************/ + +export default function GaugeCompositionPolicy(openmct) { + function hasNumericTelemetry(domainObject) { + const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject); + if (!hasTelemetry) { + return false; + } + + const metadata = openmct.telemetry.getMetadata(domainObject); + + return metadata.values().length > 0 && hasDomainAndRange(metadata); + } + + function hasDomainAndRange(metadata) { + return ( + metadata.valuesForHints(['range']).length > 0 && + metadata.valuesForHints(['domain']).length > 0 + ); + } + + return { + allow: function (parent, child) { + if (parent.type === 'gauge') { + return hasNumericTelemetry(child); + } + + return true; + } + }; +} diff --git a/src/plugins/gauge/GaugePlugin.js b/src/plugins/gauge/GaugePlugin.js index af9777f5a65..6c11cd0f9cb 100644 --- a/src/plugins/gauge/GaugePlugin.js +++ b/src/plugins/gauge/GaugePlugin.js @@ -23,6 +23,7 @@ import mount from 'utils/mount'; import GaugeFormController from './components/GaugeFormController.vue'; +import GaugeCompositionPolicy from './GaugeCompositionPolicy'; import GaugeViewProvider from './GaugeViewProvider'; export const GAUGE_TYPES = [ @@ -165,6 +166,7 @@ export default function () { } ] }); + openmct.composition.addPolicy(new GaugeCompositionPolicy(openmct).allow); }; function getGaugeFormController(openmct) { diff --git a/src/plugins/gauge/components/GaugeComponent.vue b/src/plugins/gauge/components/GaugeComponent.vue index 0a124373776..0026fcb99de 100644 --- a/src/plugins/gauge/components/GaugeComponent.vue +++ b/src/plugins/gauge/components/GaugeComponent.vue @@ -408,10 +408,10 @@ export default { const CHAR_THRESHOLD = 3; const START_PERC = 8.5; const REDUCE_PERC = 0.8; - const RANGE_CHARS_MAX = Math.max( - this.rangeLow.toString().length, - this.rangeHigh.toString().length - ); + const RANGE_CHARS_MAX = + this.rangeLow && this.rangeHigh + ? Math.max(this.rangeLow.toString().length, this.rangeHigh.toString().length) + : CHAR_THRESHOLD; return this.fontSizeFromChars(RANGE_CHARS_MAX, CHAR_THRESHOLD, START_PERC, REDUCE_PERC); },