Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Mazzella <ozyx@users.noreply.github.com>2022-11-10 23:06:13 +0300
committerGitHub <noreply@github.com>2022-11-10 23:06:13 +0300
commit7bb4a136d793044a0b21d61a5d52499a0b9e0ff6 (patch)
tree58b8a77ad5ae118e301caab2e748caa178a835a2
parent8af3b4309f9a63c928b47dd0cdcc4a8f7dd4a37a (diff)
Use Composition API to add/remove from composition (#5941)
* Use composition API in RemoveAction * refactor: ScatterPlotView to use composition API * fix: initialize transaction to null and reset * fix: remove seriesKey and correct found condition * refactor: Gauge to use composition API * refactor: DisplayLayout to use composition API * test: RemoveAction starts and ends transactions * test: add ScatterPlot add/remove telemetry test * test: fix e2e test and add annotation * test: remove unnecessary awaits * test: make some displayLayout tests stable * test{displayLayout}: navigate to objects via url * test(gauge): add test for add/remove telemetry * fix(#3117): init layoutItems within transaction * refactor: add clearSelection() method * test: remove unstable tag * fix(#3117): init frames and use transactions - fixes #3117 for flexible layouts by syncing frames and composition - also uses transactions now to avoid race condition * test(flexibleLayout): removing items via context menu - add test for removing items via context menu while focusing the layout - add test for removing items via context menu while not focusing the layout * fix(e2e): use pluginFixtures * refactor(e2e): improve selectors * refactor: use async/await for saving transactions * docs(e2e): fix comments * test: use soft expects Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
-rw-r--r--e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js28
-rw-r--r--e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js77
-rw-r--r--e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js93
-rw-r--r--e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js93
-rw-r--r--src/api/Editor.js15
-rw-r--r--src/plugins/charts/scatter/ScatterPlotView.vue6
-rw-r--r--src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue12
-rw-r--r--src/plugins/displayLayout/components/DisplayLayout.vue36
-rw-r--r--src/plugins/flexibleLayout/components/flexibleLayout.vue33
-rw-r--r--src/plugins/formActions/EditPropertiesAction.js12
-rw-r--r--src/plugins/gauge/components/Gauge.vue6
-rw-r--r--src/plugins/notebook/components/Notebook.vue20
-rw-r--r--src/plugins/remove/RemoveAction.js12
-rw-r--r--src/plugins/remove/pluginSpec.js13
14 files changed, 375 insertions, 81 deletions
diff --git a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
index a9c5e5670..a88d1a70a 100644
--- a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
+++ b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
@@ -23,7 +23,7 @@
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions');
-test.describe('Testing Display Layout @unstable', () => {
+test.describe('Display Layout', () => {
let sineWaveObject;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
@@ -55,12 +55,12 @@ test.describe('Testing Display Layout @unstable', () => {
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
- const formattedTelemetryValue = await getTelemValuePromise;
+ const formattedTelemetryValue = getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
- await expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
+ expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({ page }) => {
// Create a Display Layout
@@ -86,12 +86,12 @@ test.describe('Testing Display Layout @unstable', () => {
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
- const formattedTelemetryValue = await getTelemValuePromise;
+ const formattedTelemetryValue = getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
- await expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
+ expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
test('items in a display layout can be removed with object tree context menu when viewing the display layout', async ({ page }) => {
// Create a Display Layout
@@ -121,11 +121,15 @@ test.describe('Testing Display Layout @unstable', () => {
// delete
- expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
+ expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
test('items in a display layout can be removed with object tree context menu when viewing another item', async ({ page }) => {
+ test.info().annotations.push({
+ type: 'issue',
+ description: 'https://github.com/nasa/openmct/issues/3117'
+ });
// Create a Display Layout
- await createDomainObjectWithDefaults(page, {
+ const displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: "Test Display Layout"
});
@@ -144,18 +148,18 @@ test.describe('Testing Display Layout @unstable', () => {
// Expand the Display Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
- // Click the original Sine Wave Generator to navigate away from the Display Layout
- await page.locator('.c-tree__item .c-tree__item__name:text("Test Sine Wave Generator")').click();
+ // Go to the original Sine Wave Generator to navigate away from the Display Layout
+ await page.goto(sineWaveObject.url);
// Bring up context menu and remove
await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').click({ button: 'right' });
- await page.locator('text=Remove').click();
+ await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// navigate back to the display layout to confirm it has been removed
- await page.locator('.c-tree__item .c-tree__item__name:text("Test Display Layout")').click();
+ await page.goto(displayLayout.url);
- expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
+ expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
});
diff --git a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
index 484091d7c..b6949e4fc 100644
--- a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
+++ b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
@@ -23,12 +23,13 @@
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
-test.describe('Testing Flexible Layout @unstable', () => {
+test.describe('Flexible Layout', () => {
+ let sineWaveObject;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
// Create Sine Wave Generator
- await createDomainObjectWithDefaults(page, {
+ sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator"
});
@@ -54,13 +55,81 @@ test.describe('Testing Flexible Layout @unstable', () => {
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
await page.dragAndDrop('text=Test Clock', '.c-fl__container.is-empty');
// Check that panes can be dragged while Flexible Layout is in Edit mode
- let dragWrapper = await page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
+ let dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'true');
// Save Flexible Layout
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Check that panes are not draggable while Flexible Layout is in Browse mode
- dragWrapper = await page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
+ dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
});
+ test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({ page }) => {
+ // Create a Display Layout
+ await createDomainObjectWithDefaults(page, {
+ type: 'Flexible Layout',
+ name: "Test Flexible Layout"
+ });
+ // Edit Flexible Layout
+ await page.locator('[title="Edit"]').click();
+
+ // Expand the 'My Items' folder in the left tree
+ await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
+ // Add the Sine Wave Generator to the Flexible Layout and save changes
+ await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
+ await page.locator('button[title="Save"]').click();
+ await page.locator('text=Save and Finish Editing').click();
+
+ expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
+
+ // Expand the Flexible Layout so we can remove the sine wave generator
+ await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
+
+ // Bring up context menu and remove
+ await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').first().click({ button: 'right' });
+ await page.locator('li[role="menuitem"]:has-text("Remove")').click();
+ await page.locator('button:has-text("OK")').click();
+
+ // Verify that the item has been removed from the layout
+ expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
+ });
+ test('items in a flexible layout can be removed with object tree context menu when viewing another item', async ({ page }) => {
+ test.info().annotations.push({
+ type: 'issue',
+ description: 'https://github.com/nasa/openmct/issues/3117'
+ });
+ // Create a Flexible Layout
+ const flexibleLayout = await createDomainObjectWithDefaults(page, {
+ type: 'Flexible Layout',
+ name: "Test Flexible Layout"
+ });
+ // Edit Flexible Layout
+ await page.locator('[title="Edit"]').click();
+
+ // Expand the 'My Items' folder in the left tree
+ await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
+ // Add the Sine Wave Generator to the Flexible Layout and save changes
+ await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
+ await page.locator('button[title="Save"]').click();
+ await page.locator('text=Save and Finish Editing').click();
+
+ expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
+
+ // Expand the Flexible Layout so we can remove the sine wave generator
+ await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
+
+ // Go to the original Sine Wave Generator to navigate away from the Flexible Layout
+ await page.goto(sineWaveObject.url);
+
+ // Bring up context menu and remove
+ await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').click({ button: 'right' });
+ await page.locator('li[role="menuitem"]:has-text("Remove")').click();
+ await page.locator('button:has-text("OK")').click();
+
+ // navigate back to the display layout to confirm it has been removed
+ await page.goto(flexibleLayout.url);
+
+ // Verify that the item has been removed from the layout
+ expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
+ });
});
diff --git a/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js
new file mode 100644
index 000000000..1e36449a5
--- /dev/null
+++ b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*
+* This test suite is dedicated to testing the Gauge component.
+*/
+
+const { test, expect } = require('../../../../baseFixtures');
+const { createDomainObjectWithDefaults } = require('../../../../appActions');
+const uuid = require('uuid').v4;
+
+test.describe('Gauge', () => {
+ let gauge;
+
+ test.beforeEach(async ({ page }) => {
+ // Open a browser, navigate to the main page, and wait until all networkevents to resolve
+ await page.goto('./', { waitUntil: 'networkidle' });
+
+ // Create the gauge
+ gauge = await createDomainObjectWithDefaults(page, { type: 'Gauge' });
+ });
+
+ test('Can add and remove telemetry sources @unstable', async ({ page }) => {
+ const editButtonLocator = page.locator('button[title="Edit"]');
+ const saveButtonLocator = page.locator('button[title="Save"]');
+
+ // Create a sine wave generator within the gauge
+ const swg1 = await createDomainObjectWithDefaults(page, {
+ type: 'Sine Wave Generator',
+ name: `swg-${uuid()}`,
+ parent: gauge.uuid
+ });
+
+ // Navigate to the gauge and verify that
+ // the SWG appears in the elements pool
+ await page.goto(gauge.url);
+ await editButtonLocator.click();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
+ await saveButtonLocator.click();
+ await page.locator('li[title="Save and Finish Editing"]').click();
+
+ // Create another sine wave generator within the gauge
+ const swg2 = await createDomainObjectWithDefaults(page, {
+ type: 'Sine Wave Generator',
+ name: `swg-${uuid()}`,
+ parent: gauge.uuid
+ });
+
+ // Verify that the 'Replace telemetry source' modal appears and accept it
+ await expect.soft(page.locator('text=This action will replace the current telemetry source. Do you want to continue?')).toBeVisible();
+ await page.click('text=Ok');
+
+ // Navigate to the gauge and verify that the new SWG
+ // appears in the elements pool and the old one is gone
+ await page.goto(gauge.url);
+ await editButtonLocator.click();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
+ await saveButtonLocator.click();
+
+ // Right click on the new SWG in the elements pool and delete it
+ await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
+ button: 'right'
+ });
+ await page.locator('li[title="Remove this object from its containing object."]').click();
+
+ // Verify that the 'Remove object' confirmation modal appears and accept it
+ await expect.soft(page.locator('text=Warning! This action will remove this object. Are you sure you want to continue?')).toBeVisible();
+ await page.click('text=Ok');
+
+ // Verify that the elements pool shows no elements
+ await expect(page.locator('text="No contained elements"')).toBeVisible();
+ });
+});
diff --git a/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js
new file mode 100644
index 000000000..ccc47fa8b
--- /dev/null
+++ b/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*
+* This test suite is dedicated to testing the Scatter Plot component.
+*/
+
+const { test, expect } = require('../../../../pluginFixtures');
+const { createDomainObjectWithDefaults } = require('../../../../appActions');
+const uuid = require('uuid').v4;
+
+test.describe('Scatter Plot', () => {
+ let scatterPlot;
+
+ test.beforeEach(async ({ page }) => {
+ // Open a browser, navigate to the main page, and wait until all networkevents to resolve
+ await page.goto('./', { waitUntil: 'networkidle' });
+
+ // Create the Scatter Plot
+ scatterPlot = await createDomainObjectWithDefaults(page, { type: 'Scatter Plot' });
+ });
+
+ test('Can add and remove telemetry sources', async ({ page }) => {
+ const editButtonLocator = page.locator('button[title="Edit"]');
+ const saveButtonLocator = page.locator('button[title="Save"]');
+
+ // Create a sine wave generator within the scatter plot
+ const swg1 = await createDomainObjectWithDefaults(page, {
+ type: 'Sine Wave Generator',
+ name: `swg-${uuid()}`,
+ parent: scatterPlot.uuid
+ });
+
+ // Navigate to the scatter plot and verify that
+ // the SWG appears in the elements pool
+ await page.goto(scatterPlot.url);
+ await editButtonLocator.click();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
+ await saveButtonLocator.click();
+ await page.locator('li[title="Save and Finish Editing"]').click();
+
+ // Create another sine wave generator within the scatter plot
+ const swg2 = await createDomainObjectWithDefaults(page, {
+ type: 'Sine Wave Generator',
+ name: `swg-${uuid()}`,
+ parent: scatterPlot.uuid
+ });
+
+ // Verify that the 'Replace telemetry source' modal appears and accept it
+ await expect.soft(page.locator('text=This action will replace the current telemetry source. Do you want to continue?')).toBeVisible();
+ await page.click('text=Ok');
+
+ // Navigate to the scatter plot and verify that the new SWG
+ // appears in the elements pool and the old one is gone
+ await page.goto(scatterPlot.url);
+ await editButtonLocator.click();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden();
+ await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
+ await saveButtonLocator.click();
+
+ // Right click on the new SWG in the elements pool and delete it
+ await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
+ button: 'right'
+ });
+ await page.locator('li[title="Remove this object from its containing object."]').click();
+
+ // Verify that the 'Remove object' confirmation modal appears and accept it
+ await expect.soft(page.locator('text=Warning! This action will remove this object. Are you sure you want to continue?')).toBeVisible();
+ await page.click('text=Ok');
+
+ // Verify that the elements pool shows no elements
+ await expect(page.locator('text="No contained elements"')).toBeVisible();
+ });
+});
diff --git a/src/api/Editor.js b/src/api/Editor.js
index b9f00357b..53c731024 100644
--- a/src/api/Editor.js
+++ b/src/api/Editor.js
@@ -56,17 +56,12 @@ export default class Editor extends EventEmitter {
* Save any unsaved changes from this editing session. This will
* end the current transaction.
*/
- save() {
+ async save() {
const transaction = this.openmct.objects.getActiveTransaction();
-
- return transaction.commit()
- .then(() => {
- this.editing = false;
- this.emit('isEditing', false);
- this.openmct.objects.endTransaction();
- }).catch(error => {
- throw error;
- });
+ await transaction.commit();
+ this.editing = false;
+ this.emit('isEditing', false);
+ this.openmct.objects.endTransaction();
}
/**
diff --git a/src/plugins/charts/scatter/ScatterPlotView.vue b/src/plugins/charts/scatter/ScatterPlotView.vue
index 129a3bca9..7baf9721d 100644
--- a/src/plugins/charts/scatter/ScatterPlotView.vue
+++ b/src/plugins/charts/scatter/ScatterPlotView.vue
@@ -112,11 +112,7 @@ export default {
}
},
removeFromComposition(telemetryObject) {
- let composition = this.domainObject.composition.filter(id =>
- !this.openmct.objects.areIdsEqual(id, telemetryObject.identifier)
- );
-
- this.openmct.objects.mutate(this.domainObject, 'composition', composition);
+ this.composition.remove(telemetryObject);
},
addTelemetryObject(telemetryObject) {
// grab information we need from the added telmetry object
diff --git a/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue b/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue
index c7af21973..03dd02cd4 100644
--- a/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue
+++ b/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue
@@ -104,10 +104,14 @@ export default {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.setAxesLabels();
},
- removeSeries(series) {
- const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(series.identifier, plotSeries.identifier));
- if (index !== undefined) {
- this.$delete(this.plotSeries, index);
+ removeSeries(seriesKey) {
+ const seriesIndex = this.plotSeries.findIndex(
+ plotSeries => this.openmct.objects.areIdsEqual(seriesKey, plotSeries.identifier)
+ );
+
+ const foundSeries = seriesIndex > -1;
+ if (foundSeries) {
+ this.$delete(this.plotSeries, seriesIndex);
this.setAxesLabels();
}
},
diff --git a/src/plugins/displayLayout/components/DisplayLayout.vue b/src/plugins/displayLayout/components/DisplayLayout.vue
index 98afcba65..e20703c9e 100644
--- a/src/plugins/displayLayout/components/DisplayLayout.vue
+++ b/src/plugins/displayLayout/components/DisplayLayout.vue
@@ -245,6 +245,9 @@ export default {
});
this.gridDimensions = [wMax * this.gridSize[0], hMax * this.gridSize[1]];
},
+ clearSelection() {
+ this.$el.click();
+ },
watchDisplayResize() {
const resizeObserver = new ResizeObserver(() => this.updateGrid());
@@ -478,7 +481,7 @@ export default {
});
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems);
- this.$el.click();
+ this.clearSelection();
},
untrackItem(item) {
if (!item.identifier) {
@@ -504,15 +507,11 @@ export default {
}
if (!telemetryViewCount && !objectViewCount) {
- this.removeFromComposition(keyString);
+ this.removeFromComposition(item);
}
},
- removeFromComposition(keyString) {
- let composition = this.domainObject.composition ? this.domainObject.composition : [];
- composition = composition.filter(identifier => {
- return this.openmct.objects.makeKeyString(identifier) !== keyString;
- });
- this.mutate("composition", composition);
+ removeFromComposition(item) {
+ this.composition.remove(item);
},
initializeItems() {
this.telemetryViewMap = {};
@@ -529,7 +528,10 @@ export default {
}
});
+ this.startTransaction();
removedItems.forEach(this.removeFromConfiguration);
+
+ return this.endTransaction();
},
isItemAlreadyTracked(child) {
let found = false;
@@ -590,7 +592,7 @@ export default {
}
});
this.mutate("configuration.items", layoutItems);
- this.$el.click();
+ this.clearSelection();
},
orderItem(position, selectedItems) {
let delta = ORDERS[position];
@@ -773,7 +775,7 @@ export default {
this.$nextTick(() => {
this.openmct.objects.mutate(this.domainObject, "configuration.items", this.layoutItems);
this.openmct.objects.mutate(this.domainObject, "configuration.objectStyles", objectStyles);
- this.$el.click(); //clear selection;
+ this.clearSelection();
newDomainObjectsArray.forEach(domainObject => {
this.composition.add(domainObject);
@@ -867,6 +869,20 @@ export default {
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1; //restore selection
},
+ startTransaction() {
+ if (!this.openmct.objects.isTransactionActive()) {
+ this.transaction = this.openmct.objects.startTransaction();
+ }
+ },
+ async endTransaction() {
+ if (!this.transaction) {
+ return;
+ }
+
+ await this.transaction.commit();
+ this.openmct.objects.endTransaction();
+ this.transaction = null;
+ },
toggleGrid() {
this.showGrid = !this.showGrid;
},
diff --git a/src/plugins/flexibleLayout/components/flexibleLayout.vue b/src/plugins/flexibleLayout/components/flexibleLayout.vue
index c2f8958de..9f176ba80 100644
--- a/src/plugins/flexibleLayout/components/flexibleLayout.vue
+++ b/src/plugins/flexibleLayout/components/flexibleLayout.vue
@@ -185,10 +185,24 @@ export default {
this.composition.off('add', this.addFrame);
},
methods: {
+ containsObject(identifier) {
+ if ('composition' in this.domainObject) {
+ return this.domainObject.composition
+ .some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
+ }
+
+ return false;
+ },
buildIdentifierMap() {
this.containers.forEach(container => {
container.frames.forEach(frame => {
- let keystring = this.openmct.objects.makeKeyString(frame.domainObjectIdentifier);
+ if (!this.containsObject(frame.domainObjectIdentifier)) {
+ this.removeChildObject(frame.domainObjectIdentifier);
+
+ return;
+ }
+
+ const keystring = this.openmct.objects.makeKeyString(frame.domainObjectIdentifier);
this.identifierMap[keystring] = true;
});
});
@@ -296,11 +310,14 @@ export default {
}
},
persist(index) {
+ this.startTransaction();
if (index) {
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
} else {
this.openmct.objects.mutate(this.domainObject, 'configuration.containers', this.containers);
}
+
+ return this.endTransaction();
},
startContainerResizing(index) {
let beforeContainer = this.containers[index];
@@ -366,6 +383,20 @@ export default {
});
this.persist();
+ },
+ startTransaction() {
+ if (!this.openmct.objects.isTransactionActive()) {
+ this.transaction = this.openmct.objects.startTransaction();
+ }
+ },
+ async endTransaction() {
+ if (!this.transaction) {
+ return;
+ }
+
+ await this.transaction.commit();
+ this.openmct.objects.endTransaction();
+ this.transaction = null;
}
}
};
diff --git a/src/plugins/formActions/EditPropertiesAction.js b/src/plugins/formActions/EditPropertiesAction.js
index f2cb232f5..eb4dec2e4 100644
--- a/src/plugins/formActions/EditPropertiesAction.js
+++ b/src/plugins/formActions/EditPropertiesAction.js
@@ -52,7 +52,7 @@ export default class EditPropertiesAction extends PropertiesAction {
/**
* @private
*/
- _onSave(changes) {
+ async _onSave(changes) {
if (!this.openmct.objects.isTransactionActive()) {
this.openmct.objects.startTransaction();
}
@@ -70,14 +70,8 @@ export default class EditPropertiesAction extends PropertiesAction {
this.openmct.objects.mutate(this.domainObject, key, value);
});
const transaction = this.openmct.objects.getActiveTransaction();
-
- return transaction.commit()
- .catch(error => {
- throw error;
- }).finally(() => {
- this.openmct.objects.endTransaction();
- });
-
+ await transaction.commit();
+ this.openmct.objects.endTransaction();
} catch (error) {
this.openmct.notifications.error('Error saving objects');
console.error(error);
diff --git a/src/plugins/gauge/components/Gauge.vue b/src/plugins/gauge/components/Gauge.vue
index 2d250158e..810ddfef0 100644
--- a/src/plugins/gauge/components/Gauge.vue
+++ b/src/plugins/gauge/components/Gauge.vue
@@ -598,11 +598,7 @@ export default {
return this.round(((vPercent / 100) * 270) + DIAL_VALUE_DEG_OFFSET, 2);
},
removeFromComposition(telemetryObject = this.telemetryObject) {
- let composition = this.domainObject.composition.filter(id =>
- !this.openmct.objects.areIdsEqual(id, telemetryObject.identifier)
- );
-
- this.openmct.objects.mutate(this.domainObject, 'composition', composition);
+ this.composition.remove(telemetryObject);
},
refreshData(bounds, isTick) {
if (!isTick) {
diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue
index c242d1356..91bdbebc7 100644
--- a/src/plugins/notebook/components/Notebook.vue
+++ b/src/plugins/notebook/components/Notebook.vue
@@ -894,24 +894,16 @@ export default {
this.transaction = this.openmct.objects.startTransaction();
}
},
- saveTransaction() {
+ async saveTransaction() {
if (this.transaction !== undefined) {
- this.transaction.commit()
- .catch(error => {
- throw error;
- }).finally(() => {
- this.openmct.objects.endTransaction();
- });
+ await this.transaction.commit();
+ this.openmct.objects.endTransaction();
}
},
- cancelTransaction() {
+ async cancelTransaction() {
if (this.transaction !== undefined) {
- this.transaction.cancel()
- .catch(error => {
- throw error;
- }).finally(() => {
- this.openmct.objects.endTransaction();
- });
+ await this.transaction.cancel();
+ this.openmct.objects.endTransaction();
}
}
}
diff --git a/src/plugins/remove/RemoveAction.js b/src/plugins/remove/RemoveAction.js
index fd6700bde..0ab0a0fa8 100644
--- a/src/plugins/remove/RemoveAction.js
+++ b/src/plugins/remove/RemoveAction.js
@@ -35,6 +35,7 @@ export default class RemoveAction {
this.openmct = openmct;
this.removeFromComposition = this.removeFromComposition.bind(this); // for access to private transaction variable
+ this.#transaction = null;
}
async invoke(objectPath) {
@@ -152,16 +153,13 @@ export default class RemoveAction {
}
}
- saveTransaction() {
+ async saveTransaction() {
if (!this.#transaction) {
return;
}
- return this.#transaction.commit()
- .catch(error => {
- throw error;
- }).finally(() => {
- this.openmct.objects.endTransaction();
- });
+ await this.#transaction.commit();
+ this.openmct.objects.endTransaction();
+ this.#transaction = null;
}
}
diff --git a/src/plugins/remove/pluginSpec.js b/src/plugins/remove/pluginSpec.js
index 8b1af6304..987709b34 100644
--- a/src/plugins/remove/pluginSpec.js
+++ b/src/plugins/remove/pluginSpec.js
@@ -78,6 +78,8 @@ describe("The Remove Action plugin", () => {
spyOn(removeAction, 'removeFromComposition').and.callThrough();
spyOn(removeAction, 'inNavigationPath').and.returnValue(false);
spyOn(openmct.objects, 'mutate').and.callThrough();
+ spyOn(openmct.objects, 'startTransaction').and.callThrough();
+ spyOn(openmct.objects, 'endTransaction').and.callThrough();
removeAction.removeFromComposition(parentObject, childObject);
});
@@ -90,6 +92,17 @@ describe("The Remove Action plugin", () => {
expect(openmct.objects.mutate).toHaveBeenCalled();
expect(openmct.objects.mutate.calls.argsFor(0)[0]).toEqual(parentObject);
});
+
+ it("it should start a transaction", () => {
+ expect(openmct.objects.startTransaction).toHaveBeenCalled();
+ });
+
+ it("it should end the transaction", (done) => {
+ setTimeout(() => {
+ expect(openmct.objects.endTransaction).toHaveBeenCalled();
+ done();
+ }, 100);
+ });
});
describe("when determining the object is applicable", () => {