From cb8e09c9f9ec176884c8b8d193a9f8f01667077e Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Sat, 8 Oct 2022 09:04:38 -0700 Subject: [PATCH] Master 2.1.1 (#5858) * Update version * Don't delete annotations if there aren't any (#5829) * don't delete annotations if there aren't any * add test and align playwright-test * align core with test * added annotation describing test * Add `aria-label` for time conductor history button (#5830) * [Overlay Plot] Inspector series and legend sync fix (#5835) * fixed overlay plots to react to series removals correctly, added alias visual to elements pool aliased items * Keep transaction open on failed editor save (#5840) * do not end a transaction on a failed editor save * add unit tests for successful editor save and unsuccessful editor save * If no matching tags, do not attempt tag search (#5839) * do not attempt search if no matching tags * fix timing on test * commit again in hopes that github will run checks * add back null tag check * add some better documentation to tests Co-authored-by: Andrew Henry * Update version for master Co-authored-by: Scott Bell Co-authored-by: Jesse Mazzella Co-authored-by: Jamie V Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com> Co-authored-by: Andrew Henry --- .../notebook/notebookWithCouchDB.e2e.spec.js | 41 +++++++++- .../plugins/notebook/tags.e2e.spec.js | 25 +++++- package.json | 4 +- src/api/Editor.js | 3 +- src/api/EditorSpec.js | 80 +++++++++++++++++++ src/api/annotation/AnnotationAPI.js | 4 + src/api/annotation/AnnotationAPISpec.js | 5 ++ src/plugins/notebook/components/Notebook.vue | 4 +- .../persistence/couch/CouchSearchProvider.js | 4 + src/plugins/plot/MctPlot.vue | 3 +- src/plugins/plot/configuration/Collection.js | 2 +- .../timeConductor/ConductorHistory.vue | 1 + src/ui/components/tags/TagEditor.vue | 8 +- src/ui/inspector/ElementItem.vue | 9 ++- src/ui/inspector/elements.scss | 9 +++ src/ui/layout/search/GrandSearchSpec.js | 9 +++ 16 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 src/api/EditorSpec.js diff --git a/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js index 4edb1ff28c..97f5ab3c6c 100644 --- a/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js @@ -27,7 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround const { test, expect } = require('../../../../baseFixtures'); const { createDomainObjectWithDefaults } = require('../../../../appActions'); -test.describe('Notebook Network Request Inspection @couchdb', () => { +test.describe('Notebook Tests with CouchDB @couchdb', () => { let testNotebook; test.beforeEach(async ({ page }) => { //Navigate to baseURL @@ -221,6 +221,45 @@ test.describe('Notebook Network Request Inspection @couchdb', () => { expect(filterNonFetchRequests(addingNotebookElementsRequests).length).toBeLessThanOrEqual(4); }); + + test('Search tests', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/akhenry/openmct-yamcs/issues/69' + }); + await page.locator('text=To start a new entry, click here or drag and drop any object').click(); + await page.locator('[aria-label="Notebook Entry Input"]').click(); + await page.locator('[aria-label="Notebook Entry Input"]').fill(`First Entry`); + await page.locator('[aria-label="Notebook Entry Input"]').press('Enter'); + + // Add three tags + await page.hover(`button:has-text("Add Tag")`); + await page.locator(`button:has-text("Add Tag")`).click(); + await page.locator('[placeholder="Type to select tag"]').click(); + await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click(); + await page.waitForSelector('[aria-label="Tag"]:has-text("Science")'); + + await page.hover(`button:has-text("Add Tag")`); + await page.locator(`button:has-text("Add Tag")`).click(); + await page.locator('[placeholder="Type to select tag"]').click(); + await page.locator('[aria-label="Autocomplete Options"] >> text=Drilling').click(); + await page.waitForSelector('[aria-label="Tag"]:has-text("Drilling")'); + + await page.hover(`button:has-text("Add Tag")`); + await page.locator(`button:has-text("Add Tag")`).click(); + await page.locator('[placeholder="Type to select tag"]').click(); + await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click(); + await page.waitForSelector('[aria-label="Tag"]:has-text("Driving")'); + + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); + await expect(page.locator('[aria-label="Search Result"]').first()).toContainText("Science"); + await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText("Driving"); + + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); + await expect(page.locator('text=No results found')).toBeVisible(); + }); }); // Try to reduce indeterminism of browser requests by only returning fetch requests. diff --git a/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js b/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js index e2cda186a6..792117b3e9 100644 --- a/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js @@ -39,7 +39,7 @@ async function createNotebookAndEntry(page, iterations = 1) { createDomainObjectWithDefaults(page, { type: 'Notebook' }); for (let iteration = 0; iteration < iterations; iteration++) { - // Click text=To start a new entry, click here or drag and drop any object + // Create an entry await page.locator('text=To start a new entry, click here or drag and drop any object').click(); const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`; await page.locator(entryLocator).click(); @@ -116,7 +116,7 @@ test.describe('Tagging in Notebooks @addInit', () => { await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); - await expect(page.locator('[aria-label="Search Result"]')).toBeHidden(); + await expect(page.locator('text=No results found')).toBeVisible(); }); test('Can delete tags', async ({ page }) => { @@ -133,6 +133,27 @@ test.describe('Tagging in Notebooks @addInit', () => { await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); }); + test('Can delete entries without tags', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5823' + }); + + await createNotebookEntryAndTags(page); + + await page.locator('text=To start a new entry, click here or drag and drop any object').click(); + const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`; + await page.locator(entryLocator).click(); + await page.locator(entryLocator).fill(`An entry without tags`); + await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter'); + + await page.hover('[aria-label="Notebook Entry Input"] >> nth=1'); + await page.locator('button[title="Delete this entry"]').last().click(); + await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeVisible(); + await page.locator('button:has-text("Ok")').click(); + await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeHidden(); + }); + test('Can delete objects with tags and neither return in search', async ({ page }) => { await createNotebookEntryAndTags(page); // Delete Notebook diff --git a/package.json b/package.json index 1293f0ef08..8ea240c62e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "2.1.1-SNAPSHOT", + "version": "2.1.2", "description": "The Open MCT core platform", "devDependencies": { "@babel/eslint-parser": "7.18.9", @@ -51,7 +51,7 @@ "moment-timezone": "0.5.37", "nyc": "15.1.0", "painterro": "1.2.78", - "playwright-core": "1.26.1", + "playwright-core": "1.25.2", "plotly.js-basic-dist": "2.14.0", "plotly.js-gl2d-dist": "2.14.0", "printj": "1.3.1", diff --git a/src/api/Editor.js b/src/api/Editor.js index d919da3837..b9f00357bd 100644 --- a/src/api/Editor.js +++ b/src/api/Editor.js @@ -63,10 +63,9 @@ export default class Editor extends EventEmitter { .then(() => { this.editing = false; this.emit('isEditing', false); + this.openmct.objects.endTransaction(); }).catch(error => { throw error; - }).finally(() => { - this.openmct.objects.endTransaction(); }); } diff --git a/src/api/EditorSpec.js b/src/api/EditorSpec.js new file mode 100644 index 0000000000..104a4f6eb8 --- /dev/null +++ b/src/api/EditorSpec.js @@ -0,0 +1,80 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +import { + createOpenMct, resetApplicationState +} from '../utils/testing'; + +describe('The Editor API', () => { + let openmct; + + beforeEach((done) => { + openmct = createOpenMct(); + openmct.on('start', done); + + spyOn(openmct.objects, 'endTransaction'); + + openmct.startHeadless(); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + it('opens a transaction on edit', () => { + expect( + openmct.objects.isTransactionActive() + ).toBeFalse(); + openmct.editor.edit(); + expect( + openmct.objects.isTransactionActive() + ).toBeTrue(); + }); + + it('closes an open transaction on successful save', async () => { + spyOn(openmct.objects, 'getActiveTransaction') + .and.returnValue({ + commit: () => Promise.resolve(true) + }); + + openmct.editor.edit(); + await openmct.editor.save(); + + expect( + openmct.objects.endTransaction + ).toHaveBeenCalled(); + }); + + it('does not close an open transaction on failed save', async () => { + spyOn(openmct.objects, 'getActiveTransaction') + .and.returnValue({ + commit: () => Promise.reject() + }); + + openmct.editor.edit(); + await openmct.editor.save().catch(() => {}); + + expect( + openmct.objects.endTransaction + ).not.toHaveBeenCalled(); + }); +}); diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js index 148b1d3c4b..618d6f874f 100644 --- a/src/api/annotation/AnnotationAPI.js +++ b/src/api/annotation/AnnotationAPI.js @@ -346,6 +346,10 @@ export default class AnnotationAPI extends EventEmitter { */ async searchForTags(query, abortController) { const matchingTagKeys = this.#getMatchingTags(query); + if (!matchingTagKeys.length) { + return []; + } + const searchResults = (await Promise.all(this.openmct.objects.search(matchingTagKeys, abortController, this.openmct.objects.SEARCH_TYPES.TAGS))).flat(); const filteredDeletedResults = searchResults.filter((result) => { return !(result._deleted); diff --git a/src/api/annotation/AnnotationAPISpec.js b/src/api/annotation/AnnotationAPISpec.js index a7e2a162dd..cc362d8a18 100644 --- a/src/api/annotation/AnnotationAPISpec.js +++ b/src/api/annotation/AnnotationAPISpec.js @@ -185,5 +185,10 @@ describe("The Annotation API", () => { expect(results).toBeDefined(); expect(results.length).toEqual(1); }); + it("returns no tags for empty search", async () => { + const results = await openmct.annotation.searchForTags('q'); + expect(results).toBeDefined(); + expect(results.length).toEqual(0); + }); }); }); diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue index 327205a925..14a5cc48f1 100644 --- a/src/plugins/notebook/components/Notebook.vue +++ b/src/plugins/notebook/components/Notebook.vue @@ -515,7 +515,9 @@ export default { }); }, removeAnnotations(entryId) { - this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]); + if (this.notebookAnnotations[entryId]) { + this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]); + } }, checkEntryPos(entry) { const entryPos = getEntryPosById(entry.id, this.domainObject, this.selectedSection, this.selectedPage); diff --git a/src/plugins/persistence/couch/CouchSearchProvider.js b/src/plugins/persistence/couch/CouchSearchProvider.js index 5d9ef5e4fb..d89d130505 100644 --- a/src/plugins/persistence/couch/CouchSearchProvider.js +++ b/src/plugins/persistence/couch/CouchSearchProvider.js @@ -90,6 +90,10 @@ class CouchSearchProvider { } searchForTags(tagsArray, abortSignal) { + if (!tagsArray || !tagsArray.length) { + return []; + } + const filter = { "selector": { "$and": [ diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue index 7db95295c3..125ab557e6 100644 --- a/src/plugins/plot/MctPlot.vue +++ b/src/plugins/plot/MctPlot.vue @@ -442,7 +442,8 @@ export default { }); }, - removeSeries(plotSeries) { + removeSeries(plotSeries, index) { + this.seriesModels.splice(index, 1); this.checkSameRangeValue(); this.stopListening(plotSeries); }, diff --git a/src/plugins/plot/configuration/Collection.js b/src/plugins/plot/configuration/Collection.js index 57101ff2d8..f35a96ff64 100644 --- a/src/plugins/plot/configuration/Collection.js +++ b/src/plugins/plot/configuration/Collection.js @@ -102,8 +102,8 @@ export default class Collection extends Model { throw new Error('model not found in collection.'); } - this.emit('remove', model, index); this.models.splice(index, 1); + this.emit('remove', model, index); } destroy(model) { diff --git a/src/plugins/timeConductor/ConductorHistory.vue b/src/plugins/timeConductor/ConductorHistory.vue index 8fe3b980e6..87087c9085 100644 --- a/src/plugins/timeConductor/ConductorHistory.vue +++ b/src/plugins/timeConductor/ConductorHistory.vue @@ -26,6 +26,7 @@ >