diff options
author | Shefali Joshi <simplyrender@gmail.com> | 2022-10-08 19:04:38 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-08 19:04:38 +0300 |
commit | cb8e09c9f9ec176884c8b8d193a9f8f01667077e (patch) | |
tree | 9ff34b7848ad1addb51f7ff5a1ad2ab97db5d491 | |
parent | 026eb86f5fab06bd8a36123ba4465dac365ab8f1 (diff) |
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 <akhenry@gmail.com>
* Update version for master
Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
-rw-r--r-- | e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js | 41 | ||||
-rw-r--r-- | e2e/tests/functional/plugins/notebook/tags.e2e.spec.js | 25 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | src/api/Editor.js | 3 | ||||
-rw-r--r-- | src/api/EditorSpec.js | 80 | ||||
-rw-r--r-- | src/api/annotation/AnnotationAPI.js | 4 | ||||
-rw-r--r-- | src/api/annotation/AnnotationAPISpec.js | 5 | ||||
-rw-r--r-- | src/plugins/notebook/components/Notebook.vue | 4 | ||||
-rw-r--r-- | src/plugins/persistence/couch/CouchSearchProvider.js | 4 | ||||
-rw-r--r-- | src/plugins/plot/MctPlot.vue | 3 | ||||
-rw-r--r-- | src/plugins/plot/configuration/Collection.js | 2 | ||||
-rw-r--r-- | src/plugins/timeConductor/ConductorHistory.vue | 1 | ||||
-rw-r--r-- | src/ui/components/tags/TagEditor.vue | 8 | ||||
-rw-r--r-- | src/ui/inspector/ElementItem.vue | 9 | ||||
-rw-r--r-- | src/ui/inspector/elements.scss | 9 | ||||
-rw-r--r-- | src/ui/layout/search/GrandSearchSpec.js | 9 |
16 files changed, 195 insertions, 16 deletions
diff --git a/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js index 4edb1ff28..97f5ab3c6 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 e2cda186a..792117b3e 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 1293f0ef0..8ea240c62 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 d919da383..b9f00357b 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 000000000..104a4f6eb --- /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 148b1d3c4..618d6f874 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 a7e2a162d..cc362d8a1 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 327205a92..14a5cc48f 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 5d9ef5e4f..d89d13050 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 7db95295c..125ab557e 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 57101ff2d..f35a96ff6 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 8fe3b980e..87087c908 100644 --- a/src/plugins/timeConductor/ConductorHistory.vue +++ b/src/plugins/timeConductor/ConductorHistory.vue @@ -26,6 +26,7 @@ > <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"> <button + aria-label="Time Conductor History" class="c-button--menu c-history-button icon-history" @click.prevent.stop="showHistoryMenu" > diff --git a/src/ui/components/tags/TagEditor.vue b/src/ui/components/tags/TagEditor.vue index e1353d834..3c4f137be 100644 --- a/src/ui/components/tags/TagEditor.vue +++ b/src/ui/components/tags/TagEditor.vue @@ -145,10 +145,10 @@ export default { const annotationsToDelete = this.annotations.filter((annotation) => { return annotation.tags.includes(tagToRemove); }); - const result = await this.openmct.annotation.deleteAnnotations(annotationsToDelete); - this.$emit('tags-updated', annotationsToDelete); - - return result; + if (annotationsToDelete) { + await this.openmct.annotation.deleteAnnotations(annotationsToDelete); + this.$emit('tags-updated', annotationsToDelete); + } }, async tagAdded(newTag) { // Either undelete an annotation, or create one (1) new annotation diff --git a/src/ui/inspector/ElementItem.vue b/src/ui/inspector/ElementItem.vue index 9d7be7085..0235241b1 100644 --- a/src/ui/inspector/ElementItem.vue +++ b/src/ui/inspector/ElementItem.vue @@ -33,7 +33,8 @@ class="c-tree__item c-elements-pool__item" :class="{ 'is-context-clicked': contextClickActive, - 'hover': hover + 'hover': hover, + 'is-alias': isAlias }" > <span @@ -55,6 +56,7 @@ export default { components: { ObjectLabel }, + inject: ['openmct'], props: { index: { type: Number, @@ -82,9 +84,12 @@ export default { } }, data() { + const isAlias = this.elementObject.location !== this.openmct.objects.makeKeyString(this.parentObject.identifier); + return { contextClickActive: false, - hover: false + hover: false, + isAlias }; }, methods: { diff --git a/src/ui/inspector/elements.scss b/src/ui/inspector/elements.scss index 14d90b570..e1c519901 100644 --- a/src/ui/inspector/elements.scss +++ b/src/ui/inspector/elements.scss @@ -8,6 +8,15 @@ margin-top: $interiorMargin; } + &__item { + &.is-alias { + // Object is an alias to an original. + [class*='__type-icon'] { + @include isAlias(); + } + } + } + &__search { flex: 0 0 auto; } diff --git a/src/ui/layout/search/GrandSearchSpec.js b/src/ui/layout/search/GrandSearchSpec.js index 5235fc2b3..a4b84515a 100644 --- a/src/ui/layout/search/GrandSearchSpec.js +++ b/src/ui/layout/search/GrandSearchSpec.js @@ -232,6 +232,8 @@ describe("GrandSearch", () => { it("should render an object search result if new object added", async () => { const composition = openmct.composition.get(mockFolderObject); composition.add(mockNewObject); + // after adding, need to wait a beat for the folder to be indexed + await Vue.nextTick(); await grandSearchComponent.$children[0].searchEverything('apple'); await Vue.nextTick(); const searchResults = document.querySelectorAll('[aria-label="New Apple Test Folder folder result"]'); @@ -271,6 +273,13 @@ describe("GrandSearch", () => { expect(annotationResults[1].innerText).toContain('Driving'); }); + it("should render no annotation search results if no match", async () => { + await grandSearchComponent.$children[0].searchEverything('Qbert'); + await Vue.nextTick(); + const annotationResults = document.querySelectorAll('[aria-label="Search Result"]'); + expect(annotationResults.length).toBe(0); + }); + it("should preview object search results in edit mode if object clicked", async () => { await grandSearchComponent.$children[0].searchEverything('Folder'); grandSearchComponent._provided.openmct.router.path = [mockDisplayLayout]; |