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>
This commit is contained in:
Shefali Joshi 2022-10-08 09:04:38 -07:00 committed by GitHub
parent 026eb86f5f
commit cb8e09c9f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 195 additions and 16 deletions

View File

@ -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.

View File

@ -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

View File

@ -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",

View File

@ -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();
});
}

80
src/api/EditorSpec.js Normal file
View File

@ -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();
});
});

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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);

View File

@ -90,6 +90,10 @@ class CouchSearchProvider {
}
searchForTags(tagsArray, abortSignal) {
if (!tagsArray || !tagsArray.length) {
return [];
}
const filter = {
"selector": {
"$and": [

View File

@ -442,7 +442,8 @@ export default {
});
},
removeSeries(plotSeries) {
removeSeries(plotSeries, index) {
this.seriesModels.splice(index, 1);
this.checkSameRangeValue();
this.stopListening(plotSeries);
},

View File

@ -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) {

View File

@ -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"
>

View File

@ -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

View File

@ -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: {

View File

@ -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;
}

View File

@ -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];