From 2b2c74da9cdda4a1caebd7d7b7cefe8bc45c813c Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 25 Jan 2024 06:36:44 +0100 Subject: [PATCH] New action to reload an individual view and all of its children (#7362) * add reload action plugin * checking for domain object before reloading * check if objects are equal before refreshing * add test * lint * change to label * ensure object styles are initialized * resubscribe to staleness too * add better labels for tabels * ensure tab uses exact for label now due to table aria changes * fix table tests * make tabs exact * update conflicts --------- Co-authored-by: Jesse Mazzella --- .../reloadAction/reloadAction.e2e.spec.js | 125 ++++++++++++++++++ .../functional/plugins/tabs/tabs.e2e.spec.js | 8 +- .../telemetryTable/telemetryTable.e2e.spec.js | 19 ++- e2e/tests/performance/tabs.e2e.spec.js | 12 +- src/MCT.js | 1 + src/plugins/plugins.js | 2 + src/plugins/reloadAction/ReloadAction.js | 37 ++++++ src/plugins/reloadAction/plugin.js | 28 ++++ .../telemetryTable/components/TableCell.vue | 2 +- .../components/TableComponent.vue | 1 + src/ui/components/ObjectView.vue | 10 ++ 11 files changed, 227 insertions(+), 18 deletions(-) create mode 100644 e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js create mode 100644 src/plugins/reloadAction/ReloadAction.js create mode 100644 src/plugins/reloadAction/plugin.js diff --git a/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js new file mode 100644 index 0000000000..c64e19a007 --- /dev/null +++ b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js @@ -0,0 +1,125 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import { createDomainObjectWithDefaults, expandEntireTree } from '../../../../appActions.js'; +import { expect, test } from '../../../../pluginFixtures.js'; + +test.describe('Reload action', () => { + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + const displayLayout = await createDomainObjectWithDefaults(page, { + type: 'Display Layout' + }); + + const alphaTable = await createDomainObjectWithDefaults(page, { + type: 'Telemetry Table', + name: 'Alpha Table' + }); + + const betaTable = await createDomainObjectWithDefaults(page, { + type: 'Telemetry Table', + name: 'Beta Table' + }); + + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: alphaTable.uuid, + customParameters: { + '[aria-label="Data Rate (hz)"]': '0.001' + } + }); + + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: betaTable.uuid, + customParameters: { + '[aria-label="Data Rate (hz)"]': '0.001' + } + }); + + await page.goto(displayLayout.url); + + // Expand all folders + await expandEntireTree(page); + + await page.getByLabel('Edit Object', { exact: true }).click(); + + await page.dragAndDrop(`text='Alpha Table'`, '.l-layout__grid-holder', { + targetPosition: { x: 0, y: 0 } + }); + + await page.dragAndDrop(`text='Beta Table'`, '.l-layout__grid-holder', { + targetPosition: { x: 0, y: 250 } + }); + + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + }); + + test('can reload display layout and its children', async ({ page }) => { + const beforeReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const beforeReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + // reload alpha + await page.getByTitle('View menu items').first().click(); + await page.getByRole('menuitem', { name: /Reload/ }).click(); + + const afterReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const afterReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + + expect(beforeReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue); + expect(beforeReloadBetaTelemetryValue).toEqual(afterReloadBetaTelemetryValue); + + // now reload parent + await page.getByTitle('More actions').click(); + await page.getByRole('menuitem', { name: /Reload/ }).click(); + + const fullReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const fullReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + + expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue); + expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue); + }); +}); diff --git a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js index 7d5df80fb8..9f8c9eb8c7 100644 --- a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js +++ b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js @@ -50,7 +50,7 @@ test.describe('Tabs View', () => { page.goto(tabsView.url); // select first tab - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); @@ -58,7 +58,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas[id=webglContext]')).toBeHidden(); // select second tab - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); // ensure notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); @@ -67,7 +67,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas[id=webglContext]')).toBeHidden(); // select third tab - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // expect sine wave generator visible await expect(page.locator('.c-plot')).toBeVisible(); @@ -78,7 +78,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas').nth(1)).toBeVisible(); // now try to select the first tab again - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); diff --git a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js index 1bc7e062b6..4ce30f034e 100644 --- a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js +++ b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -64,10 +64,9 @@ test.describe('Telemetry Table', () => { // Get the most recent telemetry date const latestTelemetryDate = await page - .locator('table.c-telemetry-table__body > tbody > tr') + .getByLabel('table content') + .getByLabel('utc table cell') .last() - .locator('td') - .nth(1) .getAttribute('title'); // Verify that it is <= our new end bound @@ -91,7 +90,7 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('Roger'); - let cells = await page.getByRole('cell', { name: /Roger/ }).all(); + let cells = await page.getByRole('cell').getByText(/Roger/).all(); // ensure we've got more than one cell expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term @@ -103,7 +102,10 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('Dodger'); - cells = await page.getByRole('cell', { name: /Dodger/ }).all(); + cells = await page + .getByRole('cell') + .getByText(/Dodger/) + .all(); // ensure we've got more than one cell expect(cells.length).toBe(0); // ensure the text content of each cell contains the search term @@ -135,7 +137,7 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('/[Rr]oger/'); - let cells = await page.getByRole('cell', { name: /Roger/ }).all(); + let cells = await page.getByRole('cell').getByText(/Roger/).all(); // ensure we've got more than one cell expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term @@ -147,7 +149,10 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('/[Dd]oger/'); - cells = await page.getByRole('cell', { name: /Dodger/ }).all(); + cells = await page + .getByRole('cell') + .getByText(/Dodger/) + .all(); // ensure we've got more than one cell expect(cells.length).toBe(0); // ensure the text content of each cell contains the search term diff --git a/e2e/tests/performance/tabs.e2e.spec.js b/e2e/tests/performance/tabs.e2e.spec.js index 9689be8a38..3db219f1da 100644 --- a/e2e/tests/performance/tabs.e2e.spec.js +++ b/e2e/tests/performance/tabs.e2e.spec.js @@ -24,7 +24,7 @@ import { createDomainObjectWithDefaults, waitForPlotsToRender } from '../../appA import { expect, test } from '../../pluginFixtures.js'; test.describe('Tabs View', () => { - test('Renders tabbed elements nicely', async ({ page }) => { + test('Renders tabbed elements only when visible', async ({ page }) => { // Code to hook into the requestAnimationFrame function and log each call let animationCalls = []; await page.exposeFunction('logCall', (callCount) => { @@ -64,24 +64,24 @@ test.describe('Tabs View', () => { page.goto(tabsView.url); // select first tab - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); // select second tab - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); // expect notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); // select third tab - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // ensure sine wave generator visible expect(await page.locator('.c-plot').isVisible()).toBe(true); // now select notebook and clear animation calls - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); animationCalls = []; // expect notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); @@ -89,7 +89,7 @@ test.describe('Tabs View', () => { // select sine wave generator and clear animation calls animationCalls = []; - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // ensure sine wave generator visible await waitForPlotsToRender(page); diff --git a/src/MCT.js b/src/MCT.js index 5640995739..0a011b33ab 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -251,6 +251,7 @@ export class MCT extends EventEmitter { this.install(this.plugins.FlexibleLayout()); this.install(this.plugins.GoToOriginalAction()); this.install(this.plugins.OpenInNewTabAction()); + this.install(this.plugins.ReloadAction()); this.install(this.plugins.WebPage()); this.install(this.plugins.Condition()); this.install(this.plugins.ConditionWidget()); diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index c1c2e539ad..fb7887c9f3 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -65,6 +65,7 @@ import PerformanceIndicator from './performanceIndicator/plugin.js'; import CouchDBPlugin from './persistence/couch/plugin.js'; import PlanLayout from './plan/plugin.js'; import PlotPlugin from './plot/plugin.js'; +import ReloadAction from './reloadAction/plugin.js'; import RemoteClock from './remoteClock/plugin.js'; import StaticRootPlugin from './staticRootPlugin/plugin.js'; import SummaryWidget from './summaryWidget/plugin.js'; @@ -141,6 +142,7 @@ plugins.Filters = Filters; plugins.ObjectMigration = ObjectMigration; plugins.GoToOriginalAction = GoToOriginalAction; plugins.OpenInNewTabAction = OpenInNewTabAction; +plugins.ReloadAction = ReloadAction; plugins.ClearData = ClearData; plugins.WebPage = WebPagePlugin; plugins.Espresso = Espresso; diff --git a/src/plugins/reloadAction/ReloadAction.js b/src/plugins/reloadAction/ReloadAction.js new file mode 100644 index 0000000000..ff86bff4f5 --- /dev/null +++ b/src/plugins/reloadAction/ReloadAction.js @@ -0,0 +1,37 @@ +/***************************************************************************** + * 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 class ReloadAction { + constructor(openmct) { + this.name = 'Reload'; + this.key = 'reload'; + this.description = 'Reload this object and its children'; + this.group = 'action'; + this.priority = 10; + this.cssClass = 'icon-refresh'; + + this.openmct = openmct; + } + invoke(objectPath, view) { + const domainObject = objectPath[0]; + this.openmct.objectViews.emit('reload', domainObject); + } +} diff --git a/src/plugins/reloadAction/plugin.js b/src/plugins/reloadAction/plugin.js new file mode 100644 index 0000000000..c9db76b89a --- /dev/null +++ b/src/plugins/reloadAction/plugin.js @@ -0,0 +1,28 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import ReloadAction from './ReloadAction.js'; + +export default function plugin() { + return function install(openmct) { + openmct.actions.register(new ReloadAction(openmct)); + }; +} diff --git a/src/plugins/telemetryTable/components/TableCell.vue b/src/plugins/telemetryTable/components/TableCell.vue index 4a4fa81ff8..e30b64ce09 100644 --- a/src/plugins/telemetryTable/components/TableCell.vue +++ b/src/plugins/telemetryTable/components/TableCell.vue @@ -22,8 +22,8 @@