5361 tags not persisting locally (#5408)

* fixed typo

* remove unneeded lookup

* fix tags adding and deleting

* more reliable way to remove tags

* break tests up for parallel execution

* fixed notebook tagging test

* enable e2e tests

* made schedule index comment more clear and fix uppercase/lowercase issue

* address e2e changes

* add unit test to bump coverage

* fix typo

* need to check on annotation creation if provider exists or not

* added fixtures

* undo silly couchdb commit
This commit is contained in:
Scott Bell 2022-06-29 19:30:18 +02:00 committed by GitHub
parent 28dbd724d6
commit 2bd35bb2a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 46 deletions

View File

@ -0,0 +1,146 @@
/*****************************************************************************
* 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 tests which verify form functionality.
*/
const { expect } = require('@playwright/test');
const { test } = require('../../../fixtures');
/**
* Creates a notebook object and adds an entry.
* @param {import('@playwright/test').Page} page
*/
async function createNotebookAndEntry(page) {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click button:has-text("Create")
await page.locator('button:has-text("Create")').click();
// Click li:has-text("Notebook")
await page.locator('li:has-text("Notebook")').click();
// Click button:has-text("OK")
await Promise.all([
page.waitForNavigation(),
page.locator('button:has-text("OK")').click()
]);
// Click text=To start a new entry, click here or drag and drop any object
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
}
/**
* Creates a notebook object, adds an entry, and adds a tag.
* @param {import('@playwright/test').Page} page
*/
async function createNotebookEntryAndTags(page) {
await createNotebookAndEntry(page);
// Click text=To start a new entry, click here or drag and drop any object
await page.locator('button:has-text("Add Tag")').click();
// Click [placeholder="Type to select tag"]
await page.locator('[placeholder="Type to select tag"]').click();
// Click text=Driving
await page.locator('text=Driving').click();
// Click button:has-text("Add Tag")
await page.locator('button:has-text("Add Tag")').click();
// Click [placeholder="Type to select tag"]
await page.locator('[placeholder="Type to select tag"]').click();
// Click text=Science
await page.locator('text=Science').click();
}
test.describe('Tagging in Notebooks', () => {
test('Can load tags', async ({ page }) => {
await createNotebookAndEntry(page);
// Click text=To start a new entry, click here or drag and drop any object
await page.locator('button:has-text("Add Tag")').click();
// Click [placeholder="Type to select tag"]
await page.locator('[placeholder="Type to select tag"]').click();
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Science");
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving");
});
test('Can add tags', async ({ page }) => {
await createNotebookEntryAndTags(page);
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
// Click button:has-text("Add Tag")
await page.locator('button:has-text("Add Tag")').click();
// Click [placeholder="Type to select tag"]
await page.locator('[placeholder="Type to select tag"]').click();
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Science");
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving");
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
});
test('Can search for tags', async ({ page }) => {
await createNotebookEntryAndTags(page);
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science");
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving");
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science");
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving");
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
});
test('Can delete tags', async ({ page }) => {
await createNotebookEntryAndTags(page);
await page.locator('[aria-label="Notebook Entries"]').click();
// Delete Driving
await page.locator('text=Science Driving Add Tag >> button').nth(1).click();
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
await expect(page.locator('[aria-label="Notebook Entry"]')).not.toContainText("Driving");
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
});
test('Tags persist across reload', async ({ page }) => {
await createNotebookEntryAndTags(page);
//Go to baseURL
await page.reload();
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
});
});

View File

@ -89,7 +89,7 @@
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",

View File

@ -224,7 +224,8 @@ class InMemorySearchProvider {
/**
* Schedule an id to be indexed at a later date. If there are less
* pending requests then allowed, will kick off an indexing request.
* pending requests than the maximum allowed, this will kick off an indexing request.
* This is done only when indexing first begins and we need to index a lot of objects.
*
* @private
* @param {identifier} id to be indexed.
@ -258,8 +259,12 @@ class InMemorySearchProvider {
}
onAnnotationCreation(annotationObject) {
const provider = this;
provider.index(annotationObject);
const objectProvider = this.openmct.objects.getProvider(annotationObject.identifier);
if (objectProvider === undefined || objectProvider.search === undefined) {
const provider = this;
provider.index(annotationObject);
}
}
onNameMutation(domainObject, name) {
@ -270,7 +275,6 @@ class InMemorySearchProvider {
}
onTagMutation(domainObject, newTags) {
domainObject.oldTags = domainObject.tags;
domainObject.tags = newTags;
const provider = this;
@ -404,20 +408,16 @@ class InMemorySearchProvider {
}
});
// remove old tags
if (model.oldTags) {
model.oldTags.forEach(tagIDToRemove => {
const existsInNewModel = model.tags.includes(tagIDToRemove);
if (!existsInNewModel && this.localIndexedAnnotationsByTag[tagIDToRemove]) {
this.localIndexedAnnotationsByTag[tagIDToRemove] = this.localIndexedAnnotationsByTag[tagIDToRemove].
filter(annotationToRemove => {
const shouldKeep = annotationToRemove.keyString !== keyString;
const tagsToRemoveFromIndex = Object.keys(this.localIndexedAnnotationsByTag).filter(indexedTag => {
return !(model.tags.includes(indexedTag));
});
tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => {
this.localIndexedAnnotationsByTag[tagToRemoveFromIndex] = this.localIndexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => {
const shouldKeep = indexedAnnotation.keyString !== keyString;
return shouldKeep;
});
}
return shouldKeep;
});
}
});
}
localIndexAnnotation(objectToIndex, model) {
@ -449,7 +449,7 @@ class InMemorySearchProvider {
keyString
};
if (model && (model.type === 'annotation')) {
if (model.targets && model.targets) {
if (model.targets) {
this.localIndexAnnotation(objectToIndex, model);
}

View File

@ -94,19 +94,16 @@
});
// remove old tags
if (model.oldTags) {
model.oldTags.forEach(tagIDToRemove => {
const existsInNewModel = model.tags.includes(tagIDToRemove);
if (!existsInNewModel && indexedAnnotationsByTag[tagIDToRemove]) {
indexedAnnotationsByTag[tagIDToRemove] = indexedAnnotationsByTag[tagIDToRemove].
filter(annotationToRemove => {
const shouldKeep = annotationToRemove.keyString !== keyString;
const tagsToRemoveFromIndex = Object.keys(indexedAnnotationsByTag).filter(indexedTag => {
return !(model.tags.includes(indexedTag));
});
tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => {
indexedAnnotationsByTag[tagToRemoveFromIndex] = indexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => {
const shouldKeep = indexedAnnotation.keyString !== keyString;
return shouldKeep;
});
}
return shouldKeep;
});
}
});
}
function indexItem(keyString, model) {
@ -116,7 +113,7 @@
keyString
};
if (model && (model.type === 'annotation')) {
if (model.targets && model.targets) {
if (model.targets) {
indexAnnotation(objectToIndex, model);
}

View File

@ -144,6 +144,7 @@
v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
aria-label="Notebook Entries"
>
<NotebookEntry
v-for="entry in filteredAndSortedEntries"

View File

@ -23,6 +23,7 @@
<template>
<div
class="c-notebook__entry c-ne has-local-controls has-tag-applier"
aria-label="Notebook Entry"
:class="{ 'locked': isLocked }"
@dragover="changeCursor"
@drop.capture="cancelEditMode"

View File

@ -102,7 +102,7 @@ class CouchSearchProvider {
},
{
"model.annotationType": {
"$eq": "notebook"
"$eq": "NOTEBOOK"
}
},
{

View File

@ -100,7 +100,7 @@ export default {
}
},
async mounted() {
this.annontation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
this.annotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
this.addAnnotationListener(this.annotation);
if (this.annotation && this.annotation.tags) {
this.tagsChanged(this.annotation.tags);
@ -133,21 +133,18 @@ export default {
this.addedTags.push(newTagValue);
this.userAddingTag = true;
},
async tagRemoved(tagToRemove) {
const existingAnnotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
return this.openmct.annotation.removeAnnotationTag(existingAnnotation, tagToRemove);
tagRemoved(tagToRemove) {
return this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
},
async tagAdded(newTag) {
const existingAnnotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
const newAnnotation = await this.openmct.annotation.addAnnotationTag(existingAnnotation,
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
this.annotation = await this.openmct.annotation.addAnnotationTag(this.annotation,
this.domainObject, this.targetSpecificDetails, this.annotationType, newTag);
if (!this.annotation) {
this.addAnnotationListener(newAnnotation);
if (annotationWasCreated) {
this.addAnnotationListener(this.annotation);
}
this.tagsChanged(newAnnotation.tags);
this.tagsChanged(this.annotation.tags);
this.userAddingTag = false;
}
}

View File

@ -64,8 +64,6 @@ export default {
objectSearchResults: []
};
},
mounted() {
},
destroyed() {
document.body.removeEventListener('click', this.handleOutsideClick);
},

View File

@ -36,9 +36,9 @@ describe("GrandSearch", () => {
let parent;
let sharedWorkerToRestore;
let mockDomainObject;
let mockAnnotationObject;
let mockDisplayLayout;
let mockFolderObject;
let mockAnnotationObject;
let originalRouterPath;
beforeEach((done) => {
@ -107,7 +107,6 @@ describe("GrandSearch", () => {
};
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
const mockObjectProvider = jasmine.createSpyObj("mock object provider", [
"create",
"update",